설치 후 동작과 온보딩
설치 후 동작은 사용자가 Stripe App을 설치한 직후에 무엇이 일어날지를 결정합니다. 잘 설계된 설치 후 경험은 사용자를 설정 과정으로 자연스럽게 이끌고 활성화율을 높입니다.
설치 후 동작 유형
Stripe는 네 가지 설치 후 동작 유형을 지원하며, 각각 앱 매니페스트에서 설정합니다.
1. 앱으로 연결 (기본값)
기본 드로어 뷰포트에서 앱을 엽니다. post_install_action을 지정하지 않으면 이 동작이 기본으로 적용됩니다.
{ "post_install_action": { "type": "default" }}사용자에게는 Stripe 대시보드 사이드바에서 앱의 drawer.default 뷰포트가 표시됩니다.
2. 온보딩으로 연결
앱 전용 온보딩 화면을 열어 설정에 집중할 수 있는 경험을 제공합니다.
{ "post_install_action": { "type": "onboarding" }}이 동작을 사용하려면 매니페스트에 onboarding 뷰포트를 선언해야 합니다.
{ "ui_extension": { "views": [ { "viewport": "stripe.dashboard.onboarding", "component": "OnboardingView" } ] }, "post_install_action": { "type": "onboarding" }}3. 설정으로 연결
앱의 설정 화면을 엽니다. 사용 전에 API 키나 별도 구성이 필요한 앱에 유용합니다.
{ "post_install_action": { "type": "settings" }}이 동작을 사용하려면 settings 뷰포트가 필요합니다.
{ "ui_extension": { "views": [ { "viewport": "stripe.dashboard.settings", "component": "SettingsView" } ] }, "post_install_action": { "type": "settings" }}4. 외부 URL로 연결
설정을 위해 사용자를 외부 URL로 리디렉션합니다. 온보딩 흐름이 Stripe 대시보드 밖에 있을 때 사용하세요.
{ "post_install_action": { "type": "external", "url": "https://app.tajo.io/stripe/setup" }}Caution
외부 URL은 반드시 HTTPS를 사용해야 하며 allowed_redirect_uris에 등록하는 것이 좋습니다. Stripe 심사팀은 해당 외부 URL이 실제로 동작하는 설정 경험을 제공하는지 확인합니다.
온보딩 모범 사례
부담 없이 만들기
시작에 필요한 단계 수를 최소화하세요.
- Stripe 계정 컨텍스트에서 얻을 수 있는 정보는 미리 채워 두기
- 설정 옵션에 합리적인 기본값 사용하기
- 선택 단계는 건너뛸 수 있게 하고 나중에 완료할 경로를 분명히 제시하기
- 여러 단계로 이루어진 흐름에는 단계 표시로 진행 상황 보여주기
맞춤 설정이 가능하게 만들기
사용자가 자신의 필요에 맞게 연동을 구성할 수 있도록 하세요.
- 데이터 매핑 옵션, 어떤 Stripe 필드를 Brevo로 동기화할지 선택하게 합니다
- 동기화 주기, 실시간, 매시간, 매일 중에서 고르게 합니다
- 선택적 동기화, 어떤 고객이나 상품을 동기화할지 선택하게 합니다
- 알림 설정, 동기화 오류나 중요한 이벤트에 대한 알림을 구성하게 합니다
유용함을 바로 느끼게 만들기
가치를 즉시 보여 주세요.
- 연동을 활성화하기 전에 동기화될 데이터를 미리 보여 주기
- 설정을 마치면 무슨 일이 일어나는지 알려 주기
- 연결이 정상인지 확인할 수 있는 테스트 동기화 제공하기
- 최초 동기화가 끝난 뒤 성공 지표 표시하기
OnboardingView 컴포넌트
OnboardingView 컴포넌트는 사용자가 앱을 설치할 때 집중형 모달로 렌더링됩니다.
import { Box, Button, Inline, Icon, Banner, TextField, Select, Divider,} from '@stripe/ui-extension-sdk/ui';import type { ExtensionContextValue } from '@stripe/ui-extension-sdk/context';import { useState } from 'react';
const OnboardingView = ({ environment, userContext }: ExtensionContextValue) => { const [step, setStep] = useState(1); const [brevoApiKey, setBrevoApiKey] = useState(''); const [syncMode, setSyncMode] = useState('realtime'); const [isConnecting, setIsConnecting] = useState(false); const [error, setError] = useState<string | null>(null);
const totalSteps = 3;
const handleConnect = async () => { setIsConnecting(true); setError(null);
try { // Store the API key securely await storeBrevoApiKey(brevoApiKey);
// Verify the connection const result = await verifyBrevoConnection(brevoApiKey);
if (result.success) { setStep(2); } else { setError('Unable to connect to Brevo. Please check your API key.'); } } catch (err) { setError('Connection failed. Please try again.'); } finally { setIsConnecting(false); } };
return ( <Box css={{ padding: 'large' }}> {/* Progress indicator */} <Inline css={{ marginBottom: 'large' }}> Step {step} of {totalSteps} </Inline>
{error && ( <Banner type="critical" title="Connection Error"> {error} </Banner> )}
{step === 1 && ( <Box> <Inline css={{ fontWeight: 'bold', fontSize: 'large' }}> Connect Your Brevo Account </Inline> <Inline css={{ marginTop: 'small', color: 'secondary' }}> Enter your Brevo API key to start syncing customer data. </Inline>
<TextField label="Brevo API Key" placeholder="xkeysib-..." value={brevoApiKey} onChange={(e) => setBrevoApiKey(e.target.value)} css={{ marginTop: 'medium' }} />
<Inline css={{ marginTop: 'xsmall', color: 'secondary', fontSize: 'small' }}> Find your API key in Brevo under Settings > SMTP & API > API Keys </Inline>
<Button type="primary" onPress={handleConnect} disabled={!brevoApiKey || isConnecting} css={{ marginTop: 'medium' }} > {isConnecting ? 'Connecting...' : 'Connect Brevo'} </Button> </Box> )}
{step === 2 && ( <Box> <Inline css={{ fontWeight: 'bold', fontSize: 'large' }}> Configure Sync Settings </Inline>
<Select label="Sync Mode" value={syncMode} onChange={(value) => setSyncMode(value)} css={{ marginTop: 'medium' }} > <option value="realtime">Real-time (recommended)</option> <option value="hourly">Every hour</option> <option value="daily">Once per day</option> </Select>
<Divider css={{ marginY: 'medium' }} />
<Button type="primary" onPress={() => setStep(3)}> Continue </Button> <Button type="secondary" onPress={() => setStep(1)}> Back </Button> </Box> )}
{step === 3 && ( <Box> <Banner type="default" title="Ready to Sync"> Your Brevo account is connected. Tajo will begin syncing customer data automatically. </Banner>
<Box css={{ marginTop: 'medium' }}> <Inline css={{ fontWeight: 'bold' }}>What happens next:</Inline> <ul> <li>Existing Stripe customers will sync to Brevo contacts</li> <li>New customers and events will sync in real-time</li> <li>View sync status on any customer's detail page</li> </ul> </Box>
<Button type="primary" onPress={() => {/* Navigate to dashboard */}}> Go to Dashboard </Button> </Box> )} </Box> );};
export default OnboardingView;SignInView를 사용하는 로그인 흐름
앱에서 Tajo 같은 외부 계정으로의 로그인이 필요하다면 전용 로그인 화면을 사용하세요.
import { Box, Button, Inline, TextField, Banner, Link,} from '@stripe/ui-extension-sdk/ui';import { useState } from 'react';
const SignInView = ({ onSignInComplete }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState<string | null>(null);
const handleSignIn = async () => { setIsLoading(true); setError(null);
try { const response = await fetch('https://api.tajo.io/v1/auth/stripe-app', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), });
if (!response.ok) { throw new Error('Invalid credentials'); }
const { token } = await response.json();
// Store the auth token securely in Stripe's Secret Store await storeAuthToken(token);
onSignInComplete(); } catch (err) { setError('Sign-in failed. Please check your credentials and try again.'); } finally { setIsLoading(false); } };
return ( <Box css={{ padding: 'large' }}> <Inline css={{ fontWeight: 'bold', fontSize: 'large' }}> Sign in to Tajo </Inline> <Inline css={{ marginTop: 'small', color: 'secondary' }}> Connect your Tajo account to enable Brevo sync. </Inline>
{error && ( <Banner type="critical" title="Sign-in Failed"> {error} </Banner> )}
<TextField label="Email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} css={{ marginTop: 'medium' }} />
<TextField label="Password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} css={{ marginTop: 'small' }} />
<Button type="primary" onPress={handleSignIn} disabled={!email || !password || isLoading} css={{ marginTop: 'medium' }} > {isLoading ? 'Signing in...' : 'Sign In'} </Button>
<Link href="https://app.tajo.io/signup" external css={{ marginTop: 'small' }}> Don't have a Tajo account? Sign up </Link> </Box> );};쿼리 파라미터를 사용하는 딥 링크 실행
딥 링크의 쿼리 파라미터를 사용해 특정 온보딩 단계를 열거나 데이터를 미리 채울 수 있습니다.
import type { ExtensionContextValue } from '@stripe/ui-extension-sdk/context';
const OnboardingView = ({ environment }: ExtensionContextValue) => { // Access query parameters from the deep link const { queryParams } = environment;
// Pre-fill step from query parameter const initialStep = queryParams?.step ? parseInt(queryParams.step) : 1;
// Pre-fill API key from query parameter (e.g., from Tajo dashboard) const prefilledApiKey = queryParams?.brevo_key || '';
// Source tracking for analytics const installSource = queryParams?.source || 'marketplace';
const [step, setStep] = useState(initialStep); const [brevoApiKey, setBrevoApiKey] = useState(prefilledApiKey);
// ... rest of onboarding logic};온보딩 데이터를 미리 채우는 딥 링크는 다음과 같이 생성합니다.
// From your Tajo dashboard, generate a link that pre-fills the Brevo API keyconst onboardingLink = [ 'https://dashboard.stripe.com/live/acct_xxxxx/dashboard', '?apps[com.tajo.brevo-integration][modal]=stripe.dashboard.onboarding', '&apps[com.tajo.brevo-integration][queryParams][step]=1', '&apps[com.tajo.brevo-integration][queryParams][source]=tajo_dashboard',].join('');재방문 사용자 처리
온보딩을 마친 사용자가 앱을 다시 열면 그 상태를 감지해 알맞은 화면을 보여 주세요.
const MainView = ({ environment, userContext }: ExtensionContextValue) => { const [authState, setAuthState] = useState<'loading' | 'signed-out' | 'onboarding' | 'ready'>('loading');
useEffect(() => { checkUserState().then((state) => { setAuthState(state); }); }, []);
switch (authState) { case 'loading': return <Spinner label="Loading..." />; case 'signed-out': return <SignInView onSignInComplete={() => setAuthState('onboarding')} />; case 'onboarding': return <OnboardingView onComplete={() => setAuthState('ready')} />; case 'ready': return <DashboardView />; }};Tip
온보딩 완료 상태를 Stripe Secret Store에 저장하면 외부 API 호출 없이도 재방문 사용자를 감지할 수 있습니다.