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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +225 -0
  3. package/dist/api/client.d.ts +19 -0
  4. package/dist/api/client.d.ts.map +1 -0
  5. package/dist/api/client.js +215 -0
  6. package/dist/components/XcelPaymentFlow.d.ts +50 -0
  7. package/dist/components/XcelPaymentFlow.d.ts.map +1 -0
  8. package/dist/components/XcelPaymentFlow.js +125 -0
  9. package/dist/components/XcelPaymentScreen.d.ts +67 -0
  10. package/dist/components/XcelPaymentScreen.d.ts.map +1 -0
  11. package/dist/components/XcelPaymentScreen.js +356 -0
  12. package/dist/components/XcelPaymentWebView.d.ts +76 -0
  13. package/dist/components/XcelPaymentWebView.d.ts.map +1 -0
  14. package/dist/components/XcelPaymentWebView.js +413 -0
  15. package/dist/components/index.d.ts +12 -0
  16. package/dist/components/index.d.ts.map +1 -0
  17. package/dist/components/index.js +14 -0
  18. package/dist/context/XcelPayGateProvider.d.ts +56 -0
  19. package/dist/context/XcelPayGateProvider.d.ts.map +1 -0
  20. package/dist/context/XcelPayGateProvider.js +107 -0
  21. package/dist/hooks/use-payment-completion.d.ts +75 -0
  22. package/dist/hooks/use-payment-completion.d.ts.map +1 -0
  23. package/dist/hooks/use-payment-completion.js +181 -0
  24. package/dist/hooks/use-xcel-paygate.d.ts +96 -0
  25. package/dist/hooks/use-xcel-paygate.d.ts.map +1 -0
  26. package/dist/hooks/use-xcel-paygate.js +279 -0
  27. package/dist/index.d.ts +14 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +65 -0
  30. package/dist/services/checkout.d.ts +32 -0
  31. package/dist/services/checkout.d.ts.map +1 -0
  32. package/dist/services/checkout.js +137 -0
  33. package/dist/services/xcel-wallet.d.ts +23 -0
  34. package/dist/services/xcel-wallet.d.ts.map +1 -0
  35. package/dist/services/xcel-wallet.js +107 -0
  36. package/dist/types/index.d.ts +373 -0
  37. package/dist/types/index.d.ts.map +1 -0
  38. package/dist/types/index.js +2 -0
  39. package/dist/utils/payment-completion.d.ts +87 -0
  40. package/dist/utils/payment-completion.d.ts.map +1 -0
  41. package/dist/utils/payment-completion.js +110 -0
  42. package/dist/utils/payment-helpers.d.ts +55 -0
  43. package/dist/utils/payment-helpers.d.ts.map +1 -0
  44. package/dist/utils/payment-helpers.js +261 -0
  45. package/package.json +51 -0
  46. package/src/api/client.ts +326 -0
  47. package/src/components/XcelPaymentFlow.tsx +154 -0
  48. package/src/components/XcelPaymentScreen.tsx +477 -0
  49. package/src/components/XcelPaymentWebView.tsx +533 -0
  50. package/src/components/index.ts +14 -0
  51. package/src/context/XcelPayGateProvider.tsx +98 -0
  52. package/src/hooks/use-payment-completion.ts +225 -0
  53. package/src/hooks/use-xcel-paygate.ts +363 -0
  54. package/src/index.ts +70 -0
  55. package/src/services/checkout.ts +165 -0
  56. package/src/services/xcel-wallet.ts +175 -0
  57. package/src/types/index.ts +407 -0
  58. package/src/utils/payment-completion.ts +144 -0
  59. package/src/utils/payment-helpers.ts +287 -0
@@ -0,0 +1,533 @@
1
+ /**
2
+ * XcelPaymentWebView - WebView Payment Handler Component
3
+ *
4
+ * Handles payment flow in a WebView with automatic status detection.
5
+ * Drop this component in your navigation stack and it handles everything!
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { XcelPaymentWebView } from '@xcelapp/paygate-sdk';
10
+ *
11
+ * export default function PaymentWebViewScreen({ route }) {
12
+ * const { paymentLink, paymentCode } = route.params;
13
+ *
14
+ * return (
15
+ * <XcelPaymentWebView
16
+ * paymentLink={paymentLink}
17
+ * paymentCode={paymentCode}
18
+ * onSuccess={(result) => {
19
+ * console.log('Payment successful!', result);
20
+ * navigation.navigate('Receipt', { result });
21
+ * }}
22
+ * onFailure={(result) => {
23
+ * console.log('Payment failed', result);
24
+ * navigation.goBack();
25
+ * }}
26
+ * />
27
+ * );
28
+ * }
29
+ * ```
30
+ */
31
+
32
+ import React, { useRef, useState, useEffect, useCallback } from 'react';
33
+ import {
34
+ StyleSheet,
35
+ ActivityIndicator,
36
+ View,
37
+ Pressable,
38
+ Text,
39
+ ViewStyle,
40
+ TextStyle,
41
+ } from 'react-native';
42
+
43
+ export interface PaymentResult {
44
+ status: 'SUCCESS' | 'FAILED' | 'PENDING';
45
+ paymentCode?: string;
46
+ transactionId?: string;
47
+ url?: string;
48
+ bodyText?: string;
49
+ }
50
+
51
+ export interface XcelPaymentWebViewProps {
52
+ /** Payment link URL */
53
+ paymentLink: string;
54
+
55
+ /** Payment code for tracking */
56
+ paymentCode?: string;
57
+
58
+ /** Additional payment details */
59
+ amount?: string;
60
+ currency?: string;
61
+ description?: string;
62
+
63
+ /** Called when payment succeeds */
64
+ onSuccess?: (result: PaymentResult) => void;
65
+
66
+ /** Called when payment fails */
67
+ onFailure?: (result: PaymentResult) => void;
68
+
69
+ /** Called when payment is pending */
70
+ onPending?: (result: PaymentResult) => void;
71
+
72
+ /** Called when user closes/cancels */
73
+ onCancel?: () => void;
74
+
75
+ /** Custom header component */
76
+ renderHeader?: () => React.ReactNode;
77
+
78
+ /** Custom loading component */
79
+ renderLoading?: () => React.ReactNode;
80
+
81
+ /** Custom styles */
82
+ styles?: {
83
+ container?: ViewStyle;
84
+ header?: ViewStyle;
85
+ backButton?: ViewStyle;
86
+ backButtonText?: TextStyle;
87
+ webview?: ViewStyle;
88
+ [key: string]: ViewStyle | TextStyle | undefined;
89
+ };
90
+
91
+ /** Success detection timeout (ms) */
92
+ successTimeout?: number;
93
+
94
+ /** Show back button */
95
+ showBackButton?: boolean;
96
+ }
97
+
98
+ export function XcelPaymentWebView({
99
+ paymentLink,
100
+ paymentCode,
101
+ amount,
102
+ currency,
103
+ description,
104
+ onSuccess,
105
+ onFailure,
106
+ onPending,
107
+ onCancel,
108
+ renderHeader,
109
+ renderLoading,
110
+ styles: customStyles = {},
111
+ successTimeout = 15000,
112
+ showBackButton = true,
113
+ }: XcelPaymentWebViewProps) {
114
+ const [isLoading, setIsLoading] = useState(true);
115
+ const [isVerifying, setIsVerifying] = useState(false);
116
+ const successTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
117
+ const hasNavigatedRef = useRef(false);
118
+
119
+ // Note: WebView is imported dynamically to avoid requiring it as a dependency
120
+ // Users must install react-native-webview separately
121
+ const [WebView, setWebView] = useState<any>(null);
122
+
123
+ useEffect(() => {
124
+ // Dynamically import WebView
125
+ import('react-native-webview')
126
+ .then((module) => {
127
+ setWebView(() => module.WebView);
128
+ })
129
+ .catch((error) => {
130
+ console.error(
131
+ 'Error: react-native-webview is required. Install it with: npm install react-native-webview'
132
+ );
133
+ console.error(error);
134
+ });
135
+ }, []);
136
+
137
+ // Cleanup timer on unmount
138
+ useEffect(() => {
139
+ return () => {
140
+ if (successTimerRef.current) {
141
+ clearTimeout(successTimerRef.current);
142
+ }
143
+ };
144
+ }, []);
145
+
146
+ const handlePaymentResult = useCallback(
147
+ (result: PaymentResult) => {
148
+ if (hasNavigatedRef.current) {
149
+ return; // Prevent duplicate calls
150
+ }
151
+
152
+ hasNavigatedRef.current = true;
153
+ setIsVerifying(false);
154
+
155
+ // Clear any pending timers
156
+ if (successTimerRef.current) {
157
+ clearTimeout(successTimerRef.current);
158
+ }
159
+
160
+ const fullResult: PaymentResult = {
161
+ ...result,
162
+ paymentCode: result.paymentCode || paymentCode,
163
+ };
164
+
165
+ console.log('Payment result:', fullResult);
166
+
167
+ switch (result.status) {
168
+ case 'SUCCESS':
169
+ onSuccess?.(fullResult);
170
+ break;
171
+ case 'FAILED':
172
+ onFailure?.(fullResult);
173
+ break;
174
+ case 'PENDING':
175
+ onPending?.(fullResult);
176
+ break;
177
+ }
178
+ },
179
+ [paymentCode, onSuccess, onFailure, onPending]
180
+ );
181
+
182
+ const handleWebViewMessage = useCallback(
183
+ (event: any) => {
184
+ try {
185
+ const data = JSON.parse(event.nativeEvent.data);
186
+ console.log('WebView message:', data);
187
+
188
+ switch (data.type) {
189
+ case 'payment_success':
190
+ // Set timer before auto-navigating
191
+ if (successTimerRef.current) {
192
+ clearTimeout(successTimerRef.current);
193
+ }
194
+ successTimerRef.current = setTimeout(() => {
195
+ handlePaymentResult({
196
+ status: 'SUCCESS',
197
+ transactionId: data.url,
198
+ url: data.url,
199
+ bodyText: data.bodyText,
200
+ });
201
+ }, successTimeout);
202
+ break;
203
+
204
+ case 'payment_failed':
205
+ handlePaymentResult({
206
+ status: 'FAILED',
207
+ transactionId: data.url,
208
+ url: data.url,
209
+ bodyText: data.bodyText,
210
+ });
211
+ break;
212
+
213
+ case 'payment_pending':
214
+ handlePaymentResult({
215
+ status: 'PENDING',
216
+ transactionId: data.url,
217
+ url: data.url,
218
+ bodyText: data.bodyText,
219
+ });
220
+ break;
221
+
222
+ case 'close_clicked':
223
+ if (successTimerRef.current) {
224
+ clearTimeout(successTimerRef.current);
225
+ }
226
+ onCancel?.();
227
+ break;
228
+
229
+ case 'dom_check':
230
+ // Check for success/failure in DOM
231
+ if (data.bodyText) {
232
+ const text = data.bodyText.toLowerCase();
233
+ if (
234
+ text.includes('payment successful') ||
235
+ text.includes('transaction successful')
236
+ ) {
237
+ handleWebViewMessage({
238
+ nativeEvent: {
239
+ data: JSON.stringify({ type: 'payment_success', url: data.url }),
240
+ },
241
+ });
242
+ } else if (
243
+ text.includes('payment failed') ||
244
+ text.includes('transaction failed')
245
+ ) {
246
+ handlePaymentResult({
247
+ status: 'FAILED',
248
+ url: data.url,
249
+ bodyText: data.bodyText,
250
+ });
251
+ } else if (text.includes('payment pending')) {
252
+ handlePaymentResult({
253
+ status: 'PENDING',
254
+ url: data.url,
255
+ bodyText: data.bodyText,
256
+ });
257
+ }
258
+ }
259
+ break;
260
+ }
261
+ } catch (error) {
262
+ console.error('Error parsing WebView message:', error);
263
+ }
264
+ },
265
+ [handlePaymentResult, successTimeout, onCancel]
266
+ );
267
+
268
+ const handleNavigationStateChange = useCallback(
269
+ (navState: any) => {
270
+ const url = navState.url.toLowerCase();
271
+
272
+ // Check URL for success/failure indicators
273
+ if (url.includes('success') || url.includes('completed')) {
274
+ handleWebViewMessage({
275
+ nativeEvent: {
276
+ data: JSON.stringify({ type: 'payment_success', url: navState.url }),
277
+ },
278
+ });
279
+ } else if (url.includes('failed') || url.includes('cancel')) {
280
+ handlePaymentResult({
281
+ status: 'FAILED',
282
+ url: navState.url,
283
+ });
284
+ }
285
+ },
286
+ [handleWebViewMessage, handlePaymentResult]
287
+ );
288
+
289
+ const handleBack = () => {
290
+ if (successTimerRef.current) {
291
+ clearTimeout(successTimerRef.current);
292
+ }
293
+ onCancel?.();
294
+ };
295
+
296
+ if (!WebView) {
297
+ return (
298
+ <View style={[styles.container, customStyles.container]}>
299
+ <View style={styles.loadingContainer}>
300
+ <ActivityIndicator size="large" color="#007AFF" />
301
+ <Text style={styles.loadingText}>Loading WebView...</Text>
302
+ </View>
303
+ </View>
304
+ );
305
+ }
306
+
307
+ if (!paymentLink) {
308
+ return (
309
+ <View style={[styles.container, customStyles.container]}>
310
+ <Text style={styles.errorText}>No payment link provided</Text>
311
+ {showBackButton && (
312
+ <Pressable
313
+ style={[styles.backButton, customStyles.backButton]}
314
+ onPress={handleBack}
315
+ >
316
+ <Text style={[styles.backButtonText, customStyles.backButtonText]}>
317
+ Go Back
318
+ </Text>
319
+ </Pressable>
320
+ )}
321
+ </View>
322
+ );
323
+ }
324
+
325
+ // JavaScript to inject into WebView for payment detection
326
+ const injectedJavaScript = `
327
+ (function() {
328
+ console.log('XCEL PayGate WebView injection started');
329
+
330
+ function monitorCloseButton() {
331
+ const buttons = document.querySelectorAll('button, a, div');
332
+ buttons.forEach(btn => {
333
+ const text = btn.textContent?.toLowerCase() || '';
334
+ if (text.includes('close') || text.includes('cancel') || text.includes('back')) {
335
+ btn.addEventListener('click', () => {
336
+ window.ReactNativeWebView.postMessage(JSON.stringify({
337
+ type: 'close_clicked',
338
+ url: window.location.href
339
+ }));
340
+ });
341
+ }
342
+ });
343
+ }
344
+
345
+ function checkPaymentStatus() {
346
+ const bodyText = document.body?.innerText?.toLowerCase() || '';
347
+ const title = document.title?.toLowerCase() || '';
348
+ const successKeywords = ['payment successful', 'transaction successful', 'payment completed'];
349
+ const failureKeywords = ['payment failed', 'transaction failed', 'payment cancelled'];
350
+ const pendingKeywords = ['payment pending', 'processing'];
351
+
352
+ const hasSuccess = successKeywords.some(kw => bodyText.includes(kw) || title.includes(kw));
353
+ const hasFailure = failureKeywords.some(kw => bodyText.includes(kw) || title.includes(kw));
354
+ const hasPending = pendingKeywords.some(kw => bodyText.includes(kw) || title.includes(kw));
355
+
356
+ if (hasSuccess) {
357
+ window.ReactNativeWebView.postMessage(JSON.stringify({
358
+ type: 'payment_success',
359
+ url: window.location.href,
360
+ bodyText: bodyText.substring(0, 500)
361
+ }));
362
+ } else if (hasFailure) {
363
+ window.ReactNativeWebView.postMessage(JSON.stringify({
364
+ type: 'payment_failed',
365
+ url: window.location.href,
366
+ bodyText: bodyText.substring(0, 500)
367
+ }));
368
+ } else if (hasPending) {
369
+ window.ReactNativeWebView.postMessage(JSON.stringify({
370
+ type: 'payment_pending',
371
+ url: window.location.href,
372
+ bodyText: bodyText.substring(0, 500)
373
+ }));
374
+ } else {
375
+ window.ReactNativeWebView.postMessage(JSON.stringify({
376
+ type: 'dom_check',
377
+ url: window.location.href,
378
+ bodyText: bodyText.substring(0, 500),
379
+ title: title
380
+ }));
381
+ }
382
+ }
383
+
384
+ const observer = new MutationObserver(() => {
385
+ monitorCloseButton();
386
+ checkPaymentStatus();
387
+ });
388
+
389
+ if (document.body) {
390
+ observer.observe(document.body, { childList: true, subtree: true });
391
+ monitorCloseButton();
392
+ checkPaymentStatus();
393
+ }
394
+
395
+ window.addEventListener('load', () => {
396
+ setTimeout(() => {
397
+ monitorCloseButton();
398
+ checkPaymentStatus();
399
+ }, 1000);
400
+ });
401
+
402
+ true;
403
+ })();
404
+ `;
405
+
406
+ return (
407
+ <View style={[styles.container, customStyles.container]}>
408
+ {renderHeader ? (
409
+ renderHeader()
410
+ ) : (
411
+ <View style={[styles.header, customStyles.header]}>
412
+ {showBackButton && (
413
+ <Pressable
414
+ style={[styles.backButton, customStyles.backButton]}
415
+ onPress={handleBack}
416
+ >
417
+ <Text style={[styles.backButtonText, customStyles.backButtonText]}>
418
+ ← Back
419
+ </Text>
420
+ </Pressable>
421
+ )}
422
+ <Text style={styles.headerTitle}>Complete Payment</Text>
423
+ </View>
424
+ )}
425
+
426
+ <WebView
427
+ source={{ uri: paymentLink }}
428
+ style={[styles.webview, customStyles.webview]}
429
+ startInLoadingState={true}
430
+ javaScriptEnabled={true}
431
+ domStorageEnabled={true}
432
+ injectedJavaScript={injectedJavaScript}
433
+ onMessage={handleWebViewMessage}
434
+ onNavigationStateChange={handleNavigationStateChange}
435
+ onLoadStart={() => setIsLoading(true)}
436
+ onLoadEnd={() => setIsLoading(false)}
437
+ renderLoading={() =>
438
+ renderLoading ? (
439
+ renderLoading()
440
+ ) : (
441
+ <View style={styles.loadingContainer}>
442
+ <ActivityIndicator size="large" color="#007AFF" />
443
+ <Text style={styles.loadingText}>Loading payment page...</Text>
444
+ </View>
445
+ )
446
+ }
447
+ onError={(syntheticEvent: any) => {
448
+ const { nativeEvent } = syntheticEvent;
449
+ console.warn('WebView error:', nativeEvent);
450
+ }}
451
+ />
452
+
453
+ {isVerifying && (
454
+ <View style={styles.verifyingOverlay}>
455
+ <ActivityIndicator size="large" color="#007AFF" />
456
+ <Text style={styles.verifyingText}>Verifying payment...</Text>
457
+ </View>
458
+ )}
459
+ </View>
460
+ );
461
+ }
462
+
463
+ const styles = StyleSheet.create({
464
+ container: {
465
+ flex: 1,
466
+ backgroundColor: '#fff',
467
+ },
468
+ header: {
469
+ flexDirection: 'row',
470
+ alignItems: 'center',
471
+ paddingTop: 60,
472
+ paddingBottom: 16,
473
+ paddingHorizontal: 16,
474
+ borderBottomWidth: 1,
475
+ borderBottomColor: '#e0e0e0',
476
+ backgroundColor: '#fff',
477
+ },
478
+ backButton: {
479
+ padding: 8,
480
+ marginRight: 8,
481
+ },
482
+ backButtonText: {
483
+ fontSize: 16,
484
+ color: '#007AFF',
485
+ fontWeight: '600',
486
+ },
487
+ headerTitle: {
488
+ fontSize: 18,
489
+ fontWeight: '600',
490
+ flex: 1,
491
+ color: '#000',
492
+ },
493
+ webview: {
494
+ flex: 1,
495
+ },
496
+ loadingContainer: {
497
+ position: 'absolute',
498
+ top: 0,
499
+ left: 0,
500
+ right: 0,
501
+ bottom: 0,
502
+ justifyContent: 'center',
503
+ alignItems: 'center',
504
+ backgroundColor: '#fff',
505
+ },
506
+ loadingText: {
507
+ marginTop: 16,
508
+ fontSize: 14,
509
+ color: '#666',
510
+ },
511
+ errorText: {
512
+ fontSize: 16,
513
+ color: '#c62828',
514
+ textAlign: 'center',
515
+ marginTop: 100,
516
+ },
517
+ verifyingOverlay: {
518
+ position: 'absolute',
519
+ top: 0,
520
+ left: 0,
521
+ right: 0,
522
+ bottom: 0,
523
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
524
+ justifyContent: 'center',
525
+ alignItems: 'center',
526
+ },
527
+ verifyingText: {
528
+ color: '#fff',
529
+ marginTop: 16,
530
+ fontSize: 16,
531
+ fontWeight: '600',
532
+ },
533
+ });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * XCEL PayGate SDK - UI Components
3
+ *
4
+ * Drop-in React Native components for payment integration
5
+ */
6
+
7
+ export { XcelPaymentScreen } from './XcelPaymentScreen';
8
+ export type { XcelPaymentScreenProps } from './XcelPaymentScreen';
9
+
10
+ export { XcelPaymentWebView } from './XcelPaymentWebView';
11
+ export type { XcelPaymentWebViewProps, PaymentResult } from './XcelPaymentWebView';
12
+
13
+ export { XcelPaymentFlow } from './XcelPaymentFlow';
14
+ export type { XcelPaymentFlowProps } from './XcelPaymentFlow';
@@ -0,0 +1,98 @@
1
+ import React, { createContext, useContext, useMemo, useRef, ReactNode } from 'react';
2
+ import { XcelPayGateClient } from '../api/client';
3
+ import { CheckoutService } from '../services/checkout';
4
+ import { XcelWalletService } from '../services/xcel-wallet';
5
+ import type { XcelPayGateConfig } from '../types';
6
+
7
+ interface XcelPayGateContextValue {
8
+ client: XcelPayGateClient;
9
+ checkout: CheckoutService;
10
+ wallet: XcelWalletService;
11
+ config: XcelPayGateConfig;
12
+ }
13
+
14
+ const XcelPayGateContext = createContext<XcelPayGateContextValue | null>(null);
15
+
16
+ export interface XcelPayGateProviderProps {
17
+ config: XcelPayGateConfig;
18
+ children: ReactNode;
19
+ }
20
+
21
+ /**
22
+ * XcelPayGateProvider - Provides XCEL PayGate configuration and services to your app
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * import { XcelPayGateProvider } from '@xcelapp/paygate-sdk';
27
+ *
28
+ * function App() {
29
+ * return (
30
+ * <XcelPayGateProvider
31
+ * config={{
32
+ * merchantId: 'YOUR_MERCHANT_ID',
33
+ * publicKey: 'YOUR_PUBLIC_KEY',
34
+ * }}
35
+ * >
36
+ * <YourApp />
37
+ * </XcelPayGateProvider>
38
+ * );
39
+ * }
40
+ * ```
41
+ */
42
+ export function XcelPayGateProvider({ config, children }: XcelPayGateProviderProps) {
43
+ // Use refs to ensure services are only created once
44
+ const clientRef = useRef<XcelPayGateClient | null>(null);
45
+ const checkoutRef = useRef<CheckoutService | null>(null);
46
+ const walletRef = useRef<XcelWalletService | null>(null);
47
+
48
+ if (!clientRef.current) {
49
+ clientRef.current = new XcelPayGateClient(config);
50
+ checkoutRef.current = new CheckoutService(clientRef.current);
51
+ walletRef.current = new XcelWalletService(clientRef.current);
52
+ }
53
+
54
+ const value = useMemo(
55
+ () => ({
56
+ client: clientRef.current!,
57
+ checkout: checkoutRef.current!,
58
+ wallet: walletRef.current!,
59
+ config,
60
+ }),
61
+ [config]
62
+ );
63
+
64
+ return (
65
+ <XcelPayGateContext.Provider value={value}>
66
+ {children}
67
+ </XcelPayGateContext.Provider>
68
+ );
69
+ }
70
+
71
+ /**
72
+ * useXcelPayGateContext - Access XCEL PayGate services from anywhere in your app
73
+ *
74
+ * @throws {Error} If used outside of XcelPayGateProvider
75
+ *
76
+ * @example
77
+ * ```tsx
78
+ * import { useXcelPayGateContext } from '@xcelapp/paygate-sdk';
79
+ *
80
+ * function PaymentScreen() {
81
+ * const { client, checkout, wallet } = useXcelPayGateContext();
82
+ *
83
+ * // Use the services...
84
+ * }
85
+ * ```
86
+ */
87
+ export function useXcelPayGateContext(): XcelPayGateContextValue {
88
+ const context = useContext(XcelPayGateContext);
89
+
90
+ if (!context) {
91
+ throw new Error(
92
+ 'useXcelPayGateContext must be used within XcelPayGateProvider. ' +
93
+ 'Wrap your app with <XcelPayGateProvider> or pass config directly to hooks.'
94
+ );
95
+ }
96
+
97
+ return context;
98
+ }