Hành Động Sau Cài Đặt & Onboarding
Hành động sau cài đặt xác định điều gì xảy ra ngay sau khi người dùng cài đặt Stripe App của bạn. Trải nghiệm sau cài đặt được thiết kế tốt sẽ hướng dẫn người dùng qua quá trình thiết lập và tăng tỷ lệ kích hoạt.
Các Loại Hành Động Sau Cài Đặt
Stripe hỗ trợ bốn loại hành động sau cài đặt, mỗi loại được cấu hình trong app manifest:
1. Liên Kết Đến App (Mặc Định)
Mở app trong viewport drawer mặc định. Đây là hành vi mặc định nếu không có post_install_action được chỉ định:
{ "post_install_action": { "type": "default" }}Người dùng thấy viewport drawer.default của app trong thanh bên Stripe Dashboard.
2. Liên Kết Đến Onboarding
Mở view onboarding chuyên dụng của app, cung cấp trải nghiệm thiết lập tập trung:
{ "post_install_action": { "type": "onboarding" }}Điều này yêu cầu viewport onboarding được khai báo trong manifest:
{ "ui_extension": { "views": [ { "viewport": "stripe.dashboard.onboarding", "component": "OnboardingView" } ] }, "post_install_action": { "type": "onboarding" }}3. Liên Kết Đến Settings
Mở view settings của app, hữu ích khi app yêu cầu API keys hoặc cấu hình trước khi sử dụng:
{ "post_install_action": { "type": "settings" }}Điều này yêu cầu viewport settings:
{ "ui_extension": { "views": [ { "viewport": "stripe.dashboard.settings", "component": "SettingsView" } ] }, "post_install_action": { "type": "settings" }}4. Liên Kết Đến URL Bên Ngoài
Chuyển hướng người dùng đến URL bên ngoài để thiết lập. Sử dụng khi luồng onboarding nằm ngoài Stripe Dashboard:
{ "post_install_action": { "type": "external", "url": "https://app.tajo.io/stripe/setup" }}Caution
URL bên ngoài phải dùng HTTPS và nên được liệt kê trong allowed_redirect_uris. Nhóm đánh giá Stripe sẽ xác minh rằng URL bên ngoài cung cấp trải nghiệm thiết lập hoạt động tốt.
Các Thực Hành Tốt Nhất Cho Onboarding
Làm Cho Dễ Dàng
Giảm thiểu số bước cần thiết để bắt đầu:
- Điền trước thông tin có sẵn từ context tài khoản Stripe
- Sử dụng giá trị mặc định hợp lý cho các tùy chọn cấu hình
- Cho phép bỏ qua các bước tùy chọn với đường dẫn rõ ràng để hoàn thành sau
- Hiển thị tiến trình với chỉ số bước cho luồng nhiều bước
Làm Cho Có Thể Tùy Chỉnh
Cho phép người dùng cấu hình tích hợp theo nhu cầu:
- Tùy chọn ánh xạ dữ liệu, để người dùng chọn trường Stripe nào đồng bộ với Brevo
- Tần suất đồng bộ, cung cấp tùy chọn đồng bộ theo thời gian thực, mỗi giờ, hoặc hàng ngày
- Đồng bộ có chọn lọc, để người dùng chọn khách hàng hoặc sản phẩm nào cần đồng bộ
- Tùy chọn thông báo, cấu hình cảnh báo cho lỗi đồng bộ hoặc sự kiện quan trọng
Làm Cho Phù Hợp
Hiển thị giá trị ngay lập tức:
- Xem trước dữ liệu được đồng bộ trước khi kích hoạt tích hợp
- Cho thấy điều gì sẽ xảy ra khi người dùng hoàn thành thiết lập
- Cung cấp tùy chọn đồng bộ thử để xác minh kết nối hoạt động
- Hiển thị số liệu thành công sau khi đồng bộ ban đầu hoàn thành
Component OnboardingView
Component OnboardingView hiển thị trong modal tập trung khi người dùng cài đặt app:
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;Luồng Đăng Nhập Với SignInView
Nếu app yêu cầu người dùng đăng nhập vào tài khoản bên ngoài (như Tajo), hãy sử dụng view đăng nhập chuyên dụng:
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> );};Khởi Chạy Deep Link Với Tham Số Query
Bạn có thể khởi chạy các bước onboarding cụ thể hoặc điền trước dữ liệu bằng tham số query trong deep links:
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};Tạo deep links điền trước dữ liệu onboarding:
// 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('');Xử Lý Người Dùng Quay Lại
Khi người dùng mở app sau khi hoàn thành onboarding, hãy phát hiện trạng thái của họ và hiển thị view phù hợp:
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
Lưu trạng thái hoàn thành onboarding trong Stripe Secret Store để bạn có thể phát hiện người dùng quay lại mà không cần gọi API bên ngoài.