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,326 @@
1
+ import type {
2
+ CreateXcelTransactionRequest,
3
+ GenerateDynamicLinkRequest,
4
+ GenerateDynamicLinkResponse,
5
+ GeneratePaymentLinkRequest,
6
+ GeneratePaymentLinkResponse,
7
+ MerchantDetails,
8
+ MerchantFeesResponse,
9
+ MerchantProductsResponse,
10
+ TransactionDataResponse,
11
+ XcelAccountVerificationResponse,
12
+ XcelPayGateConfig,
13
+ XcelTransactionResponse,
14
+ } from "../types";
15
+
16
+ const DEFAULT_BASE_URL = "https://api.xcelapp.com";
17
+ const PAYGATE_BASE_URL = "https://paygate.xcelapp.com";
18
+
19
+ export class XcelPayGateClient {
20
+ private config: Required<XcelPayGateConfig>;
21
+
22
+ constructor(config: XcelPayGateConfig) {
23
+ this.config = {
24
+ ...config,
25
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
26
+ };
27
+ }
28
+
29
+ private getHeaders(): Record<string, string> {
30
+ return {
31
+ "Content-Type": "application/json",
32
+ "x-merchant-id": this.config.merchantId,
33
+ "x-public-key": this.config.publicKey,
34
+ };
35
+ }
36
+
37
+ async generatePaymentLink(
38
+ request: GeneratePaymentLinkRequest
39
+ ): Promise<GeneratePaymentLinkResponse> {
40
+ const headers = this.getHeaders();
41
+ const url = `${this.config.baseUrl}/transactions-service/paygate/generate-payment-link`;
42
+
43
+ console.log('=== [XcelPayGate] Generate Payment Link Request ===');
44
+ console.log('URL:', url);
45
+ console.log('Headers:', JSON.stringify(headers, null, 2));
46
+ console.log('Payload:', JSON.stringify(request, null, 2));
47
+
48
+ const response = await fetch(url, {
49
+ method: "POST",
50
+ headers,
51
+ body: JSON.stringify(request),
52
+ });
53
+
54
+ console.log('=== [XcelPayGate] Generate Payment Link Response ===');
55
+ console.log('Status Code:', response.status);
56
+ console.log('Status Text:', response.statusText);
57
+ console.log('Response Headers:', JSON.stringify(Object.fromEntries(response.headers.entries()), null, 2));
58
+
59
+ if (!response.ok) {
60
+ const errorText = await response.text();
61
+ console.log('Error Response Body:', errorText);
62
+ let errorMessage = `Failed to generate payment link: ${response.statusText}`;
63
+ try {
64
+ const errorData = JSON.parse(errorText);
65
+ console.log('Parsed Error Data:', JSON.stringify(errorData, null, 2));
66
+ errorMessage =
67
+ errorData.message || errorData.status_reason || errorMessage;
68
+ } catch {
69
+ errorMessage = errorText || errorMessage;
70
+ }
71
+ throw new Error(errorMessage);
72
+ }
73
+
74
+ const responseData = await response.json();
75
+ console.log('Success Response Body:', JSON.stringify(responseData, null, 2));
76
+
77
+ if (responseData.data) {
78
+ console.log('--- Response Data Details ---');
79
+ console.log('Transaction ID:', responseData.data.transaction_id);
80
+ console.log('Payment Code:', responseData.data.payment_code);
81
+ console.log('Payment Link:', responseData.data.payment_link);
82
+ console.log('Amount:', responseData.data.amount);
83
+ console.log('Currency:', responseData.data.currency);
84
+ console.log('Expires At:', responseData.data.expires_at);
85
+ }
86
+
87
+ console.log('=== End Generate Payment Link Response ===\n');
88
+
89
+ return responseData;
90
+ }
91
+
92
+ async getTransactionData(
93
+ paymentCode: string
94
+ ): Promise<TransactionDataResponse> {
95
+ const headers = this.getHeaders();
96
+ console.log('[XcelPayGate] Get Transaction Data - Headers:', JSON.stringify(headers, null, 2));
97
+ console.log('[XcelPayGate] Get Transaction Data - Payment Code:', paymentCode);
98
+
99
+ const response = await fetch(
100
+ `${this.config.baseUrl}/transactions-service/paygate/get-transaction-data/${paymentCode}`,
101
+ {
102
+ method: "GET",
103
+ headers,
104
+ }
105
+ );
106
+
107
+ if (!response.ok) {
108
+ throw new Error(`Failed to get transaction data: ${response.statusText}`);
109
+ }
110
+
111
+ const responseData = await response.json();
112
+ console.log('[XcelPayGate] Get Transaction Data - Response:', JSON.stringify(responseData, null, 2));
113
+ return responseData;
114
+ }
115
+
116
+ async getMerchantDetails(merchantId?: string): Promise<MerchantDetails> {
117
+ const id = merchantId || this.config.merchantId;
118
+ const headers = this.getHeaders();
119
+ console.log('[XcelPayGate] Get Merchant Details - Headers:', JSON.stringify(headers, null, 2));
120
+ console.log('[XcelPayGate] Get Merchant Details - Merchant ID:', id);
121
+
122
+ const response = await fetch(
123
+ `${this.config.baseUrl}/business-api/merchant/details/${id}`,
124
+ {
125
+ method: "GET",
126
+ headers,
127
+ }
128
+ );
129
+
130
+ if (!response.ok) {
131
+ throw new Error(`Failed to get merchant details: ${response.statusText}`);
132
+ }
133
+
134
+ const responseData = await response.json();
135
+ console.log('[XcelPayGate] Get Merchant Details - Response:', JSON.stringify(responseData, null, 2));
136
+ return responseData;
137
+ }
138
+
139
+ async getMerchantProducts(merchantId?: string): Promise<MerchantProductsResponse> {
140
+ const id = merchantId || this.config.merchantId;
141
+ const headers = this.getHeaders();
142
+ console.log('[XcelPayGate] Get Merchant Products - Headers:', JSON.stringify(headers, null, 2));
143
+ console.log('[XcelPayGate] Get Merchant Products - Merchant ID:', id);
144
+
145
+ const response = await fetch(
146
+ `${this.config.baseUrl}/business-api/merchant/products/${id}`,
147
+ {
148
+ method: "GET",
149
+ headers,
150
+ }
151
+ );
152
+
153
+ if (!response.ok) {
154
+ throw new Error(`Failed to get merchant products: ${response.statusText}`);
155
+ }
156
+
157
+ const responseData = await response.json();
158
+ console.log('[XcelPayGate] Get Merchant Products - Response:', JSON.stringify(responseData, null, 2));
159
+
160
+ if (responseData.data?.data && Array.isArray(responseData.data.data)) {
161
+ console.log('--- Merchant Products ---');
162
+ responseData.data.data.forEach((product: any, index: number) => {
163
+ console.log(`Product ${index + 1}:`);
164
+ console.log(' Product ID:', product.product_id);
165
+ console.log(' Product Name:', product.name);
166
+ console.log(' Active:', product.active.status);
167
+ console.log(' Web Enabled:', product.web);
168
+ console.log(' Payment Code:', product.payment_code);
169
+ });
170
+ }
171
+
172
+ return responseData;
173
+ }
174
+
175
+ async getMerchantFees(merchantId?: string): Promise<MerchantFeesResponse> {
176
+ const id = merchantId || this.config.merchantId;
177
+ const headers = this.getHeaders();
178
+ console.log('[XcelPayGate] Get Merchant Fees - Headers:', JSON.stringify(headers, null, 2));
179
+ console.log('[XcelPayGate] Get Merchant Fees - Merchant ID:', id);
180
+
181
+ const response = await fetch(
182
+ `${this.config.baseUrl}/transactions-service/merchant/charge-customer/${id}`,
183
+ {
184
+ method: "GET",
185
+ headers,
186
+ }
187
+ );
188
+
189
+ if (!response.ok) {
190
+ throw new Error(`Failed to get merchant fees: ${response.statusText}`);
191
+ }
192
+
193
+ return response.json();
194
+ }
195
+
196
+ async getMerchantAccounts(merchantId?: string): Promise<any> {
197
+ const id = merchantId || this.config.merchantId;
198
+ const headers = this.getHeaders();
199
+ console.log('[XcelPayGate] Get Merchant Accounts - Headers:', JSON.stringify(headers, null, 2));
200
+ console.log('[XcelPayGate] Get Merchant Accounts - Merchant ID:', id);
201
+
202
+ const response = await fetch(
203
+ `${this.config.baseUrl}/business-api/merchant/pos/${id}`,
204
+ {
205
+ method: "GET",
206
+ headers,
207
+ }
208
+ );
209
+
210
+ if (!response.ok) {
211
+ throw new Error(
212
+ `Failed to get merchant accounts: ${response.statusText}`
213
+ );
214
+ }
215
+
216
+ return response.json();
217
+ }
218
+
219
+ async verifyXcelAccount(
220
+ countryCode: string,
221
+ phoneNumber: string
222
+ ): Promise<XcelAccountVerificationResponse> {
223
+ const headers = this.getHeaders();
224
+ console.log('[XcelPayGate] Verify Xcel Account - Headers:', JSON.stringify(headers, null, 2));
225
+ console.log('[XcelPayGate] Verify Xcel Account - Country Code:', countryCode, 'Phone Number:', phoneNumber);
226
+
227
+ const response = await fetch(
228
+ `${this.config.baseUrl}/xas/v1/accounts/users/${countryCode}/${phoneNumber}`,
229
+ {
230
+ method: "GET",
231
+ headers,
232
+ }
233
+ );
234
+
235
+ if (!response.ok) {
236
+ throw new Error(`Failed to verify XCEL account: ${response.statusText}`);
237
+ }
238
+
239
+ return response.json();
240
+ }
241
+
242
+ async generateDynamicLink(
243
+ request: GenerateDynamicLinkRequest
244
+ ): Promise<GenerateDynamicLinkResponse> {
245
+ const headers = this.getHeaders();
246
+ console.log('[XcelPayGate] Generate Dynamic Link - Headers:', JSON.stringify(headers, null, 2));
247
+ console.log('[XcelPayGate] Generate Dynamic Link - Payload:', JSON.stringify(request, null, 2));
248
+
249
+ const response = await fetch(
250
+ `${this.config.baseUrl}/esa/otp/generate/dynamic-link`,
251
+ {
252
+ method: "POST",
253
+ headers,
254
+ body: JSON.stringify(request),
255
+ }
256
+ );
257
+
258
+ if (!response.ok) {
259
+ throw new Error(
260
+ `Failed to generate dynamic link: ${response.statusText}`
261
+ );
262
+ }
263
+
264
+ return response.json();
265
+ }
266
+
267
+ async createXcelTransaction(
268
+ request: CreateXcelTransactionRequest
269
+ ): Promise<XcelTransactionResponse> {
270
+ const headers = this.getHeaders();
271
+ console.log('[XcelPayGate] Create Xcel Transaction - Headers:', JSON.stringify(headers, null, 2));
272
+ console.log('[XcelPayGate] Create Xcel Transaction - Payload:', JSON.stringify(request, null, 2));
273
+
274
+ const response = await fetch(
275
+ `${this.config.baseUrl}/xas/v1/pos/create_transaction`,
276
+ {
277
+ method: "POST",
278
+ headers,
279
+ body: JSON.stringify(request),
280
+ }
281
+ );
282
+
283
+ if (!response.ok) {
284
+ throw new Error(
285
+ `Failed to create XCEL transaction: ${response.statusText}`
286
+ );
287
+ }
288
+
289
+ return response.json();
290
+ }
291
+
292
+ async getXcelTransactionStatus(
293
+ merchantId: string,
294
+ externalReference: string
295
+ ): Promise<XcelTransactionResponse> {
296
+ const headers = this.getHeaders();
297
+ console.log('[XcelPayGate] Get Xcel Transaction Status - Headers:', JSON.stringify(headers, null, 2));
298
+ console.log('[XcelPayGate] Get Xcel Transaction Status - Merchant ID:', merchantId, 'External Reference:', externalReference);
299
+
300
+ const response = await fetch(
301
+ `${this.config.baseUrl}/xas/v1/pos/transaction/${merchantId}/${externalReference}`,
302
+ {
303
+ method: "GET",
304
+ headers,
305
+ }
306
+ );
307
+
308
+ if (!response.ok) {
309
+ throw new Error(
310
+ `Failed to get transaction status: ${response.statusText}`
311
+ );
312
+ }
313
+
314
+ return response.json();
315
+ }
316
+
317
+ getCheckoutUrl(merchantId?: string): string {
318
+ const id = merchantId || this.config.merchantId;
319
+ return `${PAYGATE_BASE_URL}/pay/${id}`;
320
+ }
321
+
322
+ getFullPaymentUrl(paymentCode: string, merchantId?: string): string {
323
+ const checkoutUrl = this.getCheckoutUrl(merchantId);
324
+ return `${checkoutUrl}?code=${paymentCode}`;
325
+ }
326
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * XcelPaymentFlow - Complete Payment Flow Component
3
+ *
4
+ * All-in-one component that handles:
5
+ * - Payment form UI
6
+ * - WebView navigation
7
+ * - Payment completion
8
+ * - Receipt display
9
+ *
10
+ * Just drop this in your app and you're done!
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * import { XcelPaymentFlow } from '@xcelapp/paygate-sdk';
15
+ *
16
+ * export default function App() {
17
+ * return (
18
+ * <XcelPaymentFlow
19
+ * config={{
20
+ * merchantId: 'your-merchant-id',
21
+ * publicKey: 'your-public-key',
22
+ * }}
23
+ * onPaymentComplete={(result) => {
24
+ * console.log('Payment done!', result);
25
+ * }}
26
+ * />
27
+ * );
28
+ * }
29
+ * ```
30
+ */
31
+
32
+ import React, { useState } from 'react';
33
+ import { View, StyleSheet, Modal, SafeAreaView } from 'react-native';
34
+ import { XcelPaymentScreen, XcelPaymentScreenProps } from './XcelPaymentScreen';
35
+ import { XcelPaymentWebView, PaymentResult } from './XcelPaymentWebView';
36
+ import type { XcelPayGateConfig } from '../types';
37
+
38
+ export interface XcelPaymentFlowProps {
39
+ /** SDK Configuration */
40
+ config?: XcelPayGateConfig;
41
+
42
+ /** Called when payment completes (success/fail) */
43
+ onPaymentComplete?: (result: PaymentResult) => void;
44
+
45
+ /** Called when payment is cancelled */
46
+ onCancel?: () => void;
47
+
48
+ /** Pass through props to XcelPaymentScreen */
49
+ screenProps?: Partial<XcelPaymentScreenProps>;
50
+
51
+ /** Show receipt screen (implement your own or use default) */
52
+ renderReceipt?: (result: PaymentResult) => React.ReactNode;
53
+
54
+ /** Use modal for WebView (default: true) */
55
+ useModal?: boolean;
56
+ }
57
+
58
+ export function XcelPaymentFlow({
59
+ config,
60
+ onPaymentComplete,
61
+ onCancel,
62
+ screenProps = {},
63
+ renderReceipt,
64
+ useModal = true,
65
+ }: XcelPaymentFlowProps) {
66
+ const [paymentLink, setPaymentLink] = useState<string | null>(null);
67
+ const [paymentCode, setPaymentCode] = useState<string | null>(null);
68
+ const [showWebView, setShowWebView] = useState(false);
69
+ const [paymentResult, setPaymentResult] = useState<PaymentResult | null>(null);
70
+
71
+ const handlePaymentLinkGenerated = (link: string, code: string) => {
72
+ setPaymentLink(link);
73
+ setPaymentCode(code);
74
+ setShowWebView(true);
75
+ };
76
+
77
+ const handleSuccess = (result: PaymentResult) => {
78
+ setPaymentResult(result);
79
+ setShowWebView(false);
80
+ onPaymentComplete?.(result);
81
+ };
82
+
83
+ const handleFailure = (result: PaymentResult) => {
84
+ setPaymentResult(result);
85
+ setShowWebView(false);
86
+ onPaymentComplete?.(result);
87
+ };
88
+
89
+ const handlePending = (result: PaymentResult) => {
90
+ setPaymentResult(result);
91
+ setShowWebView(false);
92
+ onPaymentComplete?.(result);
93
+ };
94
+
95
+ const handleCancelWebView = () => {
96
+ setShowWebView(false);
97
+ onCancel?.();
98
+ };
99
+
100
+ // Show receipt if available
101
+ if (paymentResult && renderReceipt) {
102
+ return <>{renderReceipt(paymentResult)}</>;
103
+ }
104
+
105
+ const webViewContent = paymentLink && (
106
+ <XcelPaymentWebView
107
+ paymentLink={paymentLink}
108
+ paymentCode={paymentCode || undefined}
109
+ onSuccess={handleSuccess}
110
+ onFailure={handleFailure}
111
+ onPending={handlePending}
112
+ onCancel={handleCancelWebView}
113
+ />
114
+ );
115
+
116
+ return (
117
+ <View style={styles.container}>
118
+ {/* Payment Form */}
119
+ <XcelPaymentScreen
120
+ config={config}
121
+ onPaymentLinkGenerated={handlePaymentLinkGenerated}
122
+ {...screenProps}
123
+ />
124
+
125
+ {/* WebView Modal or Inline */}
126
+ {showWebView && (
127
+ useModal ? (
128
+ <Modal
129
+ visible={showWebView}
130
+ animationType="slide"
131
+ presentationStyle="fullScreen"
132
+ onRequestClose={handleCancelWebView}
133
+ >
134
+ <SafeAreaView style={styles.modalContainer}>
135
+ {webViewContent}
136
+ </SafeAreaView>
137
+ </Modal>
138
+ ) : (
139
+ webViewContent
140
+ )
141
+ )}
142
+ </View>
143
+ );
144
+ }
145
+
146
+ const styles = StyleSheet.create({
147
+ container: {
148
+ flex: 1,
149
+ },
150
+ modalContainer: {
151
+ flex: 1,
152
+ backgroundColor: '#fff',
153
+ },
154
+ });