xcel-paygate-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +225 -0
- package/dist/api/client.d.ts +19 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +215 -0
- package/dist/components/XcelPaymentFlow.d.ts +50 -0
- package/dist/components/XcelPaymentFlow.d.ts.map +1 -0
- package/dist/components/XcelPaymentFlow.js +125 -0
- package/dist/components/XcelPaymentScreen.d.ts +67 -0
- package/dist/components/XcelPaymentScreen.d.ts.map +1 -0
- package/dist/components/XcelPaymentScreen.js +356 -0
- package/dist/components/XcelPaymentWebView.d.ts +76 -0
- package/dist/components/XcelPaymentWebView.d.ts.map +1 -0
- package/dist/components/XcelPaymentWebView.js +413 -0
- package/dist/components/index.d.ts +12 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +14 -0
- package/dist/context/XcelPayGateProvider.d.ts +56 -0
- package/dist/context/XcelPayGateProvider.d.ts.map +1 -0
- package/dist/context/XcelPayGateProvider.js +107 -0
- package/dist/hooks/use-payment-completion.d.ts +75 -0
- package/dist/hooks/use-payment-completion.d.ts.map +1 -0
- package/dist/hooks/use-payment-completion.js +181 -0
- package/dist/hooks/use-xcel-paygate.d.ts +96 -0
- package/dist/hooks/use-xcel-paygate.d.ts.map +1 -0
- package/dist/hooks/use-xcel-paygate.js +279 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/services/checkout.d.ts +32 -0
- package/dist/services/checkout.d.ts.map +1 -0
- package/dist/services/checkout.js +137 -0
- package/dist/services/xcel-wallet.d.ts +23 -0
- package/dist/services/xcel-wallet.d.ts.map +1 -0
- package/dist/services/xcel-wallet.js +107 -0
- package/dist/types/index.d.ts +373 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/payment-completion.d.ts +87 -0
- package/dist/utils/payment-completion.d.ts.map +1 -0
- package/dist/utils/payment-completion.js +110 -0
- package/dist/utils/payment-helpers.d.ts +55 -0
- package/dist/utils/payment-helpers.d.ts.map +1 -0
- package/dist/utils/payment-helpers.js +261 -0
- package/package.json +51 -0
- package/src/api/client.ts +326 -0
- package/src/components/XcelPaymentFlow.tsx +154 -0
- package/src/components/XcelPaymentScreen.tsx +477 -0
- package/src/components/XcelPaymentWebView.tsx +533 -0
- package/src/components/index.ts +14 -0
- package/src/context/XcelPayGateProvider.tsx +98 -0
- package/src/hooks/use-payment-completion.ts +225 -0
- package/src/hooks/use-xcel-paygate.ts +363 -0
- package/src/index.ts +70 -0
- package/src/services/checkout.ts +165 -0
- package/src/services/xcel-wallet.ts +175 -0
- package/src/types/index.ts +407 -0
- package/src/utils/payment-completion.ts +144 -0
- package/src/utils/payment-helpers.ts +287 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XcelPaymentScreen - Complete Payment UI Component
|
|
3
|
+
*
|
|
4
|
+
* Drop-in payment screen with full UI and logic.
|
|
5
|
+
* Just import and use - handles everything!
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { XcelPaymentScreen } from '@xcelapp/paygate-sdk';
|
|
10
|
+
*
|
|
11
|
+
* export default function PaymentPage() {
|
|
12
|
+
* return (
|
|
13
|
+
* <XcelPaymentScreen
|
|
14
|
+
* config={{
|
|
15
|
+
* merchantId: 'your-merchant-id',
|
|
16
|
+
* publicKey: 'your-public-key',
|
|
17
|
+
* }}
|
|
18
|
+
* onPaymentComplete={(result) => {
|
|
19
|
+
* console.log('Payment complete:', result);
|
|
20
|
+
* }}
|
|
21
|
+
* />
|
|
22
|
+
* );
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import React, { useState } from 'react';
|
|
28
|
+
import {
|
|
29
|
+
ActivityIndicator,
|
|
30
|
+
Alert,
|
|
31
|
+
Pressable,
|
|
32
|
+
ScrollView,
|
|
33
|
+
StyleSheet,
|
|
34
|
+
Text,
|
|
35
|
+
TextInput,
|
|
36
|
+
View,
|
|
37
|
+
ViewStyle,
|
|
38
|
+
TextStyle,
|
|
39
|
+
} from 'react-native';
|
|
40
|
+
import { useCheckout, usePaymentPolling } from '../hooks/use-xcel-paygate';
|
|
41
|
+
import type { XcelPayGateConfig, PaymentRequest, TransactionData } from '../types';
|
|
42
|
+
|
|
43
|
+
export interface XcelPaymentScreenProps {
|
|
44
|
+
/** SDK Configuration */
|
|
45
|
+
config?: XcelPayGateConfig;
|
|
46
|
+
|
|
47
|
+
/** Optional: Custom styles */
|
|
48
|
+
styles?: {
|
|
49
|
+
container?: ViewStyle;
|
|
50
|
+
button?: ViewStyle;
|
|
51
|
+
buttonText?: TextStyle;
|
|
52
|
+
input?: ViewStyle;
|
|
53
|
+
[key: string]: ViewStyle | TextStyle | undefined;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Optional: Pre-filled form values */
|
|
57
|
+
defaultValues?: {
|
|
58
|
+
amount?: string;
|
|
59
|
+
email?: string;
|
|
60
|
+
phone?: string;
|
|
61
|
+
description?: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** Optional: Customize payment request */
|
|
65
|
+
paymentConfig?: Partial<Omit<PaymentRequest, 'amount' | 'currency'>>;
|
|
66
|
+
|
|
67
|
+
/** Called when payment link is generated */
|
|
68
|
+
onPaymentLinkGenerated?: (paymentLink: string, paymentCode: string) => void;
|
|
69
|
+
|
|
70
|
+
/** Called when payment is complete (success/fail) */
|
|
71
|
+
onPaymentComplete?: (transaction: TransactionData) => void;
|
|
72
|
+
|
|
73
|
+
/** Called on error */
|
|
74
|
+
onError?: (error: Error) => void;
|
|
75
|
+
|
|
76
|
+
/** Show status check button */
|
|
77
|
+
showStatusButton?: boolean;
|
|
78
|
+
|
|
79
|
+
/** Enable auto-polling */
|
|
80
|
+
enablePolling?: boolean;
|
|
81
|
+
|
|
82
|
+
/** Custom button text */
|
|
83
|
+
buttonText?: string;
|
|
84
|
+
|
|
85
|
+
/** Currency (default: XAF) */
|
|
86
|
+
currency?: string;
|
|
87
|
+
|
|
88
|
+
/** Hide form, only show button */
|
|
89
|
+
minimalMode?: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function XcelPaymentScreen({
|
|
93
|
+
config,
|
|
94
|
+
styles: customStyles = {},
|
|
95
|
+
defaultValues = {},
|
|
96
|
+
paymentConfig = {},
|
|
97
|
+
onPaymentLinkGenerated,
|
|
98
|
+
onPaymentComplete,
|
|
99
|
+
onError,
|
|
100
|
+
showStatusButton = true,
|
|
101
|
+
enablePolling = false,
|
|
102
|
+
buttonText = 'Generate Payment Link',
|
|
103
|
+
currency = 'XAF',
|
|
104
|
+
minimalMode = false,
|
|
105
|
+
}: XcelPaymentScreenProps) {
|
|
106
|
+
// Form state
|
|
107
|
+
const [amount, setAmount] = useState(defaultValues.amount || '1000');
|
|
108
|
+
const [email, setEmail] = useState(defaultValues.email || '');
|
|
109
|
+
const [phone, setPhone] = useState(defaultValues.phone || '');
|
|
110
|
+
const [description, setDescription] = useState(defaultValues.description || '');
|
|
111
|
+
|
|
112
|
+
// SDK Hook
|
|
113
|
+
const {
|
|
114
|
+
initiatePayment,
|
|
115
|
+
checkStatus,
|
|
116
|
+
loading,
|
|
117
|
+
error,
|
|
118
|
+
paymentLink,
|
|
119
|
+
paymentCode,
|
|
120
|
+
transaction,
|
|
121
|
+
} = useCheckout(config || undefined);
|
|
122
|
+
|
|
123
|
+
// Optional polling - only if enabled
|
|
124
|
+
const pollingEnabled = enablePolling && !!paymentCode;
|
|
125
|
+
const pollingResult = pollingEnabled
|
|
126
|
+
? usePaymentPolling(config || null, paymentCode, {
|
|
127
|
+
enabled: true,
|
|
128
|
+
maxAttempts: 24,
|
|
129
|
+
intervalMs: 5000,
|
|
130
|
+
})
|
|
131
|
+
: { result: null, isPolling: false };
|
|
132
|
+
|
|
133
|
+
const { result, isPolling } = pollingResult;
|
|
134
|
+
|
|
135
|
+
// Handle payment initiation
|
|
136
|
+
const handlePay = async () => {
|
|
137
|
+
try {
|
|
138
|
+
const response = await initiatePayment({
|
|
139
|
+
amount,
|
|
140
|
+
currency,
|
|
141
|
+
client_transaction_id: `TXN-${Date.now()}`,
|
|
142
|
+
customer_email: email,
|
|
143
|
+
customer_phone: phone,
|
|
144
|
+
description: description,
|
|
145
|
+
channel: 'WEB',
|
|
146
|
+
redirect_url: 'https://business.xcelapp.com/#/auth',
|
|
147
|
+
...paymentConfig,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const link = response.data.payment_link;
|
|
151
|
+
const code = response.data.payment_code;
|
|
152
|
+
|
|
153
|
+
console.log('✓ Payment Link Generated:', link);
|
|
154
|
+
console.log('✓ Payment Code:', code);
|
|
155
|
+
|
|
156
|
+
onPaymentLinkGenerated?.(link, code);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
const errorObj = err instanceof Error ? err : new Error('Payment failed');
|
|
159
|
+
console.error('Payment Error:', errorObj);
|
|
160
|
+
onError?.(errorObj);
|
|
161
|
+
Alert.alert('Error', errorObj.message);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Handle status check
|
|
166
|
+
const handleCheckStatus = async () => {
|
|
167
|
+
if (!paymentCode) {
|
|
168
|
+
Alert.alert('Error', 'No payment code available');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const txn = await checkStatus();
|
|
174
|
+
Alert.alert(
|
|
175
|
+
'Payment Status',
|
|
176
|
+
`Status: ${txn.status}\nAmount: ${txn.amount} ${txn.currency}`
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (onPaymentComplete && (txn.status === 'SUCCESS' || txn.status === 'FAILED')) {
|
|
180
|
+
onPaymentComplete(txn);
|
|
181
|
+
}
|
|
182
|
+
} catch (err) {
|
|
183
|
+
const errorObj = err instanceof Error ? err : new Error('Status check failed');
|
|
184
|
+
onError?.(errorObj);
|
|
185
|
+
Alert.alert('Error', errorObj.message);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Handle polling result - result is PaymentResult, not TransactionData
|
|
190
|
+
// We don't call onPaymentComplete here since it expects TransactionData
|
|
191
|
+
// User should use check status manually if using polling
|
|
192
|
+
|
|
193
|
+
// Minimal mode - just show payment link if available
|
|
194
|
+
if (minimalMode && paymentLink) {
|
|
195
|
+
return (
|
|
196
|
+
<View style={[styles.container, customStyles.container]}>
|
|
197
|
+
<Text style={styles.title}>Payment Link Ready</Text>
|
|
198
|
+
<Text style={styles.paymentLink}>{paymentLink}</Text>
|
|
199
|
+
<Text style={styles.label}>Payment Code: {paymentCode}</Text>
|
|
200
|
+
{showStatusButton && (
|
|
201
|
+
<Pressable
|
|
202
|
+
style={[styles.secondaryButton, customStyles.button]}
|
|
203
|
+
onPress={handleCheckStatus}
|
|
204
|
+
>
|
|
205
|
+
<Text style={[styles.secondaryButtonText, customStyles.buttonText]}>
|
|
206
|
+
Check Status
|
|
207
|
+
</Text>
|
|
208
|
+
</Pressable>
|
|
209
|
+
)}
|
|
210
|
+
</View>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<ScrollView style={[styles.container, customStyles.container]}>
|
|
216
|
+
<View style={styles.content}>
|
|
217
|
+
<Text style={styles.title}>XCEL PayGate</Text>
|
|
218
|
+
<Text style={styles.subtitle}>Secure Payment Processing</Text>
|
|
219
|
+
|
|
220
|
+
{/* Payment Form */}
|
|
221
|
+
{!minimalMode && (
|
|
222
|
+
<View style={styles.section}>
|
|
223
|
+
<Text style={styles.sectionTitle}>Payment Details</Text>
|
|
224
|
+
|
|
225
|
+
<Text style={styles.label}>Amount ({currency})</Text>
|
|
226
|
+
<TextInput
|
|
227
|
+
style={[styles.input, customStyles.input]}
|
|
228
|
+
value={amount}
|
|
229
|
+
onChangeText={setAmount}
|
|
230
|
+
placeholder="1000"
|
|
231
|
+
keyboardType="decimal-pad"
|
|
232
|
+
/>
|
|
233
|
+
|
|
234
|
+
<Text style={styles.label}>Customer Email (Optional)</Text>
|
|
235
|
+
<TextInput
|
|
236
|
+
style={[styles.input, customStyles.input]}
|
|
237
|
+
value={email}
|
|
238
|
+
onChangeText={setEmail}
|
|
239
|
+
placeholder="customer@example.com"
|
|
240
|
+
keyboardType="email-address"
|
|
241
|
+
autoCapitalize="none"
|
|
242
|
+
/>
|
|
243
|
+
|
|
244
|
+
<Text style={styles.label}>Customer Phone (Optional)</Text>
|
|
245
|
+
<TextInput
|
|
246
|
+
style={[styles.input, customStyles.input]}
|
|
247
|
+
value={phone}
|
|
248
|
+
onChangeText={setPhone}
|
|
249
|
+
placeholder="237233429972"
|
|
250
|
+
keyboardType="phone-pad"
|
|
251
|
+
/>
|
|
252
|
+
|
|
253
|
+
<Text style={styles.label}>Description (Optional)</Text>
|
|
254
|
+
<TextInput
|
|
255
|
+
style={[styles.input, customStyles.input]}
|
|
256
|
+
value={description}
|
|
257
|
+
onChangeText={setDescription}
|
|
258
|
+
placeholder="Payment description"
|
|
259
|
+
/>
|
|
260
|
+
</View>
|
|
261
|
+
)}
|
|
262
|
+
|
|
263
|
+
{/* Generate Payment Button */}
|
|
264
|
+
<Pressable
|
|
265
|
+
style={[
|
|
266
|
+
styles.button,
|
|
267
|
+
customStyles.button,
|
|
268
|
+
loading && styles.buttonDisabled,
|
|
269
|
+
]}
|
|
270
|
+
onPress={handlePay}
|
|
271
|
+
disabled={loading}
|
|
272
|
+
>
|
|
273
|
+
{loading ? (
|
|
274
|
+
<ActivityIndicator color="#fff" />
|
|
275
|
+
) : (
|
|
276
|
+
<Text style={[styles.buttonText, customStyles.buttonText]}>
|
|
277
|
+
{buttonText}
|
|
278
|
+
</Text>
|
|
279
|
+
)}
|
|
280
|
+
</Pressable>
|
|
281
|
+
|
|
282
|
+
{/* Payment Result Section */}
|
|
283
|
+
{paymentCode && (
|
|
284
|
+
<View style={styles.resultSection}>
|
|
285
|
+
<Text style={styles.resultTitle}>✓ Payment Created</Text>
|
|
286
|
+
|
|
287
|
+
<View style={styles.resultRow}>
|
|
288
|
+
<Text style={styles.resultLabel}>Payment Code:</Text>
|
|
289
|
+
<Text style={styles.resultValue}>{paymentCode}</Text>
|
|
290
|
+
</View>
|
|
291
|
+
|
|
292
|
+
{paymentLink && (
|
|
293
|
+
<View style={styles.resultRow}>
|
|
294
|
+
<Text style={styles.resultLabel}>Payment Link:</Text>
|
|
295
|
+
<Text style={[styles.resultValue, styles.linkText]} numberOfLines={1}>
|
|
296
|
+
{paymentLink}
|
|
297
|
+
</Text>
|
|
298
|
+
</View>
|
|
299
|
+
)}
|
|
300
|
+
|
|
301
|
+
{transaction && (
|
|
302
|
+
<>
|
|
303
|
+
<View style={styles.resultRow}>
|
|
304
|
+
<Text style={styles.resultLabel}>Status:</Text>
|
|
305
|
+
<Text style={[styles.resultValue, styles.statusText]}>
|
|
306
|
+
{transaction.status}
|
|
307
|
+
</Text>
|
|
308
|
+
</View>
|
|
309
|
+
|
|
310
|
+
<View style={styles.resultRow}>
|
|
311
|
+
<Text style={styles.resultLabel}>Transaction ID:</Text>
|
|
312
|
+
<Text style={styles.resultValue}>
|
|
313
|
+
{transaction.transaction_id}
|
|
314
|
+
</Text>
|
|
315
|
+
</View>
|
|
316
|
+
</>
|
|
317
|
+
)}
|
|
318
|
+
|
|
319
|
+
{isPolling && (
|
|
320
|
+
<Text style={styles.pollingText}>Checking payment status...</Text>
|
|
321
|
+
)}
|
|
322
|
+
|
|
323
|
+
{/* Manual Status Check */}
|
|
324
|
+
{showStatusButton && (
|
|
325
|
+
<Pressable
|
|
326
|
+
style={[styles.secondaryButton, customStyles.button]}
|
|
327
|
+
onPress={handleCheckStatus}
|
|
328
|
+
>
|
|
329
|
+
<Text style={[styles.secondaryButtonText, customStyles.buttonText]}>
|
|
330
|
+
Check Status Manually
|
|
331
|
+
</Text>
|
|
332
|
+
</Pressable>
|
|
333
|
+
)}
|
|
334
|
+
</View>
|
|
335
|
+
)}
|
|
336
|
+
|
|
337
|
+
{/* Error Display */}
|
|
338
|
+
{error && (
|
|
339
|
+
<View style={styles.errorBox}>
|
|
340
|
+
<Text style={styles.errorText}>⚠️ {error.message}</Text>
|
|
341
|
+
</View>
|
|
342
|
+
)}
|
|
343
|
+
</View>
|
|
344
|
+
</ScrollView>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const styles = StyleSheet.create({
|
|
349
|
+
container: {
|
|
350
|
+
flex: 1,
|
|
351
|
+
backgroundColor: '#fff',
|
|
352
|
+
},
|
|
353
|
+
content: {
|
|
354
|
+
padding: 20,
|
|
355
|
+
},
|
|
356
|
+
title: {
|
|
357
|
+
fontSize: 24,
|
|
358
|
+
fontWeight: 'bold',
|
|
359
|
+
marginBottom: 8,
|
|
360
|
+
textAlign: 'center',
|
|
361
|
+
color: '#000',
|
|
362
|
+
},
|
|
363
|
+
subtitle: {
|
|
364
|
+
fontSize: 16,
|
|
365
|
+
marginBottom: 24,
|
|
366
|
+
textAlign: 'center',
|
|
367
|
+
color: '#666',
|
|
368
|
+
},
|
|
369
|
+
section: {
|
|
370
|
+
marginBottom: 24,
|
|
371
|
+
padding: 16,
|
|
372
|
+
borderRadius: 8,
|
|
373
|
+
backgroundColor: '#f5f5f5',
|
|
374
|
+
},
|
|
375
|
+
sectionTitle: {
|
|
376
|
+
fontSize: 18,
|
|
377
|
+
fontWeight: '600',
|
|
378
|
+
marginBottom: 16,
|
|
379
|
+
color: '#000',
|
|
380
|
+
},
|
|
381
|
+
label: {
|
|
382
|
+
marginBottom: 8,
|
|
383
|
+
fontWeight: '600',
|
|
384
|
+
color: '#333',
|
|
385
|
+
},
|
|
386
|
+
input: {
|
|
387
|
+
backgroundColor: '#fff',
|
|
388
|
+
borderWidth: 1,
|
|
389
|
+
borderColor: '#ddd',
|
|
390
|
+
borderRadius: 8,
|
|
391
|
+
padding: 12,
|
|
392
|
+
marginBottom: 16,
|
|
393
|
+
fontSize: 16,
|
|
394
|
+
},
|
|
395
|
+
button: {
|
|
396
|
+
backgroundColor: '#007AFF',
|
|
397
|
+
padding: 16,
|
|
398
|
+
borderRadius: 8,
|
|
399
|
+
alignItems: 'center',
|
|
400
|
+
marginBottom: 16,
|
|
401
|
+
},
|
|
402
|
+
buttonDisabled: {
|
|
403
|
+
opacity: 0.6,
|
|
404
|
+
},
|
|
405
|
+
buttonText: {
|
|
406
|
+
color: '#fff',
|
|
407
|
+
fontSize: 16,
|
|
408
|
+
fontWeight: '600',
|
|
409
|
+
},
|
|
410
|
+
secondaryButton: {
|
|
411
|
+
backgroundColor: '#f0f0f0',
|
|
412
|
+
padding: 12,
|
|
413
|
+
borderRadius: 8,
|
|
414
|
+
alignItems: 'center',
|
|
415
|
+
marginTop: 8,
|
|
416
|
+
},
|
|
417
|
+
secondaryButtonText: {
|
|
418
|
+
color: '#007AFF',
|
|
419
|
+
fontSize: 14,
|
|
420
|
+
fontWeight: '600',
|
|
421
|
+
},
|
|
422
|
+
resultSection: {
|
|
423
|
+
padding: 16,
|
|
424
|
+
borderRadius: 8,
|
|
425
|
+
backgroundColor: '#e8f5e9',
|
|
426
|
+
marginBottom: 16,
|
|
427
|
+
},
|
|
428
|
+
resultTitle: {
|
|
429
|
+
fontSize: 18,
|
|
430
|
+
fontWeight: 'bold',
|
|
431
|
+
marginBottom: 16,
|
|
432
|
+
color: '#2e7d32',
|
|
433
|
+
},
|
|
434
|
+
resultRow: {
|
|
435
|
+
marginBottom: 12,
|
|
436
|
+
},
|
|
437
|
+
resultLabel: {
|
|
438
|
+
fontWeight: '600',
|
|
439
|
+
marginBottom: 4,
|
|
440
|
+
color: '#333',
|
|
441
|
+
},
|
|
442
|
+
resultValue: {
|
|
443
|
+
fontSize: 14,
|
|
444
|
+
color: '#000',
|
|
445
|
+
},
|
|
446
|
+
linkText: {
|
|
447
|
+
fontSize: 12,
|
|
448
|
+
color: '#007AFF',
|
|
449
|
+
},
|
|
450
|
+
statusText: {
|
|
451
|
+
fontWeight: 'bold',
|
|
452
|
+
fontSize: 16,
|
|
453
|
+
},
|
|
454
|
+
pollingText: {
|
|
455
|
+
marginTop: 12,
|
|
456
|
+
fontStyle: 'italic',
|
|
457
|
+
color: '#666',
|
|
458
|
+
},
|
|
459
|
+
errorBox: {
|
|
460
|
+
backgroundColor: '#ffebee',
|
|
461
|
+
padding: 16,
|
|
462
|
+
borderRadius: 8,
|
|
463
|
+
marginBottom: 16,
|
|
464
|
+
},
|
|
465
|
+
errorText: {
|
|
466
|
+
color: '#c62828',
|
|
467
|
+
fontWeight: '500',
|
|
468
|
+
},
|
|
469
|
+
paymentLink: {
|
|
470
|
+
fontSize: 12,
|
|
471
|
+
color: '#007AFF',
|
|
472
|
+
marginBottom: 16,
|
|
473
|
+
padding: 12,
|
|
474
|
+
backgroundColor: '#f5f5f5',
|
|
475
|
+
borderRadius: 8,
|
|
476
|
+
},
|
|
477
|
+
});
|