@reevit/core 0.5.1 → 0.5.9
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/dist/index.d.mts +19 -3
- package/dist/index.d.ts +19 -3
- package/dist/index.js +44 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +43 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -83,12 +83,18 @@ interface PaymentError {
|
|
|
83
83
|
details?: Record<string, unknown>;
|
|
84
84
|
}
|
|
85
85
|
interface ReevitTheme {
|
|
86
|
-
/** Primary brand color */
|
|
86
|
+
/** Primary brand color (main text color) */
|
|
87
87
|
primaryColor?: string;
|
|
88
|
-
/** Primary text color on brand surfaces */
|
|
88
|
+
/** Primary text color on brand surfaces (description/secondary text) */
|
|
89
89
|
primaryForegroundColor?: string;
|
|
90
|
+
/** Button background color */
|
|
91
|
+
buttonBackgroundColor?: string;
|
|
92
|
+
/** Button text color */
|
|
93
|
+
buttonTextColor?: string;
|
|
90
94
|
/** Background color */
|
|
91
95
|
backgroundColor?: string;
|
|
96
|
+
/** Border color for borders and dividers */
|
|
97
|
+
borderColor?: string;
|
|
92
98
|
/** Surface color for cards/panels */
|
|
93
99
|
surfaceColor?: string;
|
|
94
100
|
/** Text color */
|
|
@@ -174,6 +180,8 @@ interface PaymentIntent {
|
|
|
174
180
|
availableMethods: PaymentMethod[];
|
|
175
181
|
/** Reference provided or generated */
|
|
176
182
|
reference?: string;
|
|
183
|
+
/** Organization ID (from Reevit backend, required for webhook routing) */
|
|
184
|
+
orgId?: string;
|
|
177
185
|
/** Connection ID (from Reevit backend) */
|
|
178
186
|
connectionId?: string;
|
|
179
187
|
/** Provider name (from backend) */
|
|
@@ -229,6 +237,7 @@ interface CreatePaymentIntentRequest {
|
|
|
229
237
|
}
|
|
230
238
|
interface PaymentIntentResponse {
|
|
231
239
|
id: string;
|
|
240
|
+
org_id?: string;
|
|
232
241
|
connection_id: string;
|
|
233
242
|
provider: string;
|
|
234
243
|
status: string;
|
|
@@ -294,6 +303,12 @@ interface ReevitAPIClientConfig {
|
|
|
294
303
|
/** Request timeout in milliseconds */
|
|
295
304
|
timeout?: number;
|
|
296
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Generates a deterministic idempotency key based on input parameters
|
|
308
|
+
* Uses a simple hash function suitable for browser environments
|
|
309
|
+
* Exported for use by SDK hooks (e.g., payment link flows)
|
|
310
|
+
*/
|
|
311
|
+
declare function generateIdempotencyKey(params: Record<string, unknown>): string;
|
|
297
312
|
/**
|
|
298
313
|
* Reevit API Client
|
|
299
314
|
*/
|
|
@@ -304,6 +319,7 @@ declare class ReevitAPIClient {
|
|
|
304
319
|
constructor(config: ReevitAPIClientConfig);
|
|
305
320
|
/**
|
|
306
321
|
* Makes an authenticated API request
|
|
322
|
+
* @param idempotencyKey Optional deterministic idempotency key for the request
|
|
307
323
|
*/
|
|
308
324
|
private request;
|
|
309
325
|
/**
|
|
@@ -446,4 +462,4 @@ declare function createInitialState(): ReevitState;
|
|
|
446
462
|
*/
|
|
447
463
|
declare function reevitReducer(state: ReevitState, action: ReevitAction): ReevitState;
|
|
448
464
|
|
|
449
|
-
export { type APIErrorResponse, type CardFormData, type CheckoutProviderOption, type CheckoutState, type ConfirmPaymentRequest, type CreatePaymentIntentRequest, type HubtelSessionResponse, type MobileMoneyFormData, type MobileMoneyNetwork, type PSPConfig, type PSPType, type PaymentDetailResponse, type PaymentError, type PaymentIntent, type PaymentIntentResponse, type PaymentMethod, type PaymentResult, type PaymentSource, ReevitAPIClient, type ReevitAPIClientConfig, type ReevitAction, type ReevitCheckoutCallbacks, type ReevitCheckoutConfig, type ReevitState, type ReevitTheme, cn, createInitialState, createReevitClient, createThemeVariables, detectCountryFromCurrency, detectNetwork, formatAmount, formatPhone, generateReference, reevitReducer, validatePhone };
|
|
465
|
+
export { type APIErrorResponse, type CardFormData, type CheckoutProviderOption, type CheckoutState, type ConfirmPaymentRequest, type CreatePaymentIntentRequest, type HubtelSessionResponse, type MobileMoneyFormData, type MobileMoneyNetwork, type PSPConfig, type PSPType, type PaymentDetailResponse, type PaymentError, type PaymentIntent, type PaymentIntentResponse, type PaymentMethod, type PaymentResult, type PaymentSource, ReevitAPIClient, type ReevitAPIClientConfig, type ReevitAction, type ReevitCheckoutCallbacks, type ReevitCheckoutConfig, type ReevitState, type ReevitTheme, cn, createInitialState, createReevitClient, createThemeVariables, detectCountryFromCurrency, detectNetwork, formatAmount, formatPhone, generateIdempotencyKey, generateReference, reevitReducer, validatePhone };
|
package/dist/index.d.ts
CHANGED
|
@@ -83,12 +83,18 @@ interface PaymentError {
|
|
|
83
83
|
details?: Record<string, unknown>;
|
|
84
84
|
}
|
|
85
85
|
interface ReevitTheme {
|
|
86
|
-
/** Primary brand color */
|
|
86
|
+
/** Primary brand color (main text color) */
|
|
87
87
|
primaryColor?: string;
|
|
88
|
-
/** Primary text color on brand surfaces */
|
|
88
|
+
/** Primary text color on brand surfaces (description/secondary text) */
|
|
89
89
|
primaryForegroundColor?: string;
|
|
90
|
+
/** Button background color */
|
|
91
|
+
buttonBackgroundColor?: string;
|
|
92
|
+
/** Button text color */
|
|
93
|
+
buttonTextColor?: string;
|
|
90
94
|
/** Background color */
|
|
91
95
|
backgroundColor?: string;
|
|
96
|
+
/** Border color for borders and dividers */
|
|
97
|
+
borderColor?: string;
|
|
92
98
|
/** Surface color for cards/panels */
|
|
93
99
|
surfaceColor?: string;
|
|
94
100
|
/** Text color */
|
|
@@ -174,6 +180,8 @@ interface PaymentIntent {
|
|
|
174
180
|
availableMethods: PaymentMethod[];
|
|
175
181
|
/** Reference provided or generated */
|
|
176
182
|
reference?: string;
|
|
183
|
+
/** Organization ID (from Reevit backend, required for webhook routing) */
|
|
184
|
+
orgId?: string;
|
|
177
185
|
/** Connection ID (from Reevit backend) */
|
|
178
186
|
connectionId?: string;
|
|
179
187
|
/** Provider name (from backend) */
|
|
@@ -229,6 +237,7 @@ interface CreatePaymentIntentRequest {
|
|
|
229
237
|
}
|
|
230
238
|
interface PaymentIntentResponse {
|
|
231
239
|
id: string;
|
|
240
|
+
org_id?: string;
|
|
232
241
|
connection_id: string;
|
|
233
242
|
provider: string;
|
|
234
243
|
status: string;
|
|
@@ -294,6 +303,12 @@ interface ReevitAPIClientConfig {
|
|
|
294
303
|
/** Request timeout in milliseconds */
|
|
295
304
|
timeout?: number;
|
|
296
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Generates a deterministic idempotency key based on input parameters
|
|
308
|
+
* Uses a simple hash function suitable for browser environments
|
|
309
|
+
* Exported for use by SDK hooks (e.g., payment link flows)
|
|
310
|
+
*/
|
|
311
|
+
declare function generateIdempotencyKey(params: Record<string, unknown>): string;
|
|
297
312
|
/**
|
|
298
313
|
* Reevit API Client
|
|
299
314
|
*/
|
|
@@ -304,6 +319,7 @@ declare class ReevitAPIClient {
|
|
|
304
319
|
constructor(config: ReevitAPIClientConfig);
|
|
305
320
|
/**
|
|
306
321
|
* Makes an authenticated API request
|
|
322
|
+
* @param idempotencyKey Optional deterministic idempotency key for the request
|
|
307
323
|
*/
|
|
308
324
|
private request;
|
|
309
325
|
/**
|
|
@@ -446,4 +462,4 @@ declare function createInitialState(): ReevitState;
|
|
|
446
462
|
*/
|
|
447
463
|
declare function reevitReducer(state: ReevitState, action: ReevitAction): ReevitState;
|
|
448
464
|
|
|
449
|
-
export { type APIErrorResponse, type CardFormData, type CheckoutProviderOption, type CheckoutState, type ConfirmPaymentRequest, type CreatePaymentIntentRequest, type HubtelSessionResponse, type MobileMoneyFormData, type MobileMoneyNetwork, type PSPConfig, type PSPType, type PaymentDetailResponse, type PaymentError, type PaymentIntent, type PaymentIntentResponse, type PaymentMethod, type PaymentResult, type PaymentSource, ReevitAPIClient, type ReevitAPIClientConfig, type ReevitAction, type ReevitCheckoutCallbacks, type ReevitCheckoutConfig, type ReevitState, type ReevitTheme, cn, createInitialState, createReevitClient, createThemeVariables, detectCountryFromCurrency, detectNetwork, formatAmount, formatPhone, generateReference, reevitReducer, validatePhone };
|
|
465
|
+
export { type APIErrorResponse, type CardFormData, type CheckoutProviderOption, type CheckoutState, type ConfirmPaymentRequest, type CreatePaymentIntentRequest, type HubtelSessionResponse, type MobileMoneyFormData, type MobileMoneyNetwork, type PSPConfig, type PSPType, type PaymentDetailResponse, type PaymentError, type PaymentIntent, type PaymentIntentResponse, type PaymentMethod, type PaymentResult, type PaymentSource, ReevitAPIClient, type ReevitAPIClientConfig, type ReevitAction, type ReevitCheckoutCallbacks, type ReevitCheckoutConfig, type ReevitState, type ReevitTheme, cn, createInitialState, createReevitClient, createThemeVariables, detectCountryFromCurrency, detectNetwork, formatAmount, formatPhone, generateIdempotencyKey, generateReference, reevitReducer, validatePhone };
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ __export(index_exports, {
|
|
|
29
29
|
detectNetwork: () => detectNetwork,
|
|
30
30
|
formatAmount: () => formatAmount,
|
|
31
31
|
formatPhone: () => formatPhone,
|
|
32
|
+
generateIdempotencyKey: () => generateIdempotencyKey,
|
|
32
33
|
generateReference: () => generateReference,
|
|
33
34
|
reevitReducer: () => reevitReducer,
|
|
34
35
|
validatePhone: () => validatePhone
|
|
@@ -52,6 +53,18 @@ function createPaymentError(response, errorData) {
|
|
|
52
53
|
}
|
|
53
54
|
};
|
|
54
55
|
}
|
|
56
|
+
function generateIdempotencyKey(params) {
|
|
57
|
+
const sortedKeys = Object.keys(params).sort();
|
|
58
|
+
const stableString = sortedKeys.map((key) => `${key}:${JSON.stringify(params[key])}`).join("|");
|
|
59
|
+
let hash = 5381;
|
|
60
|
+
for (let i = 0; i < stableString.length; i++) {
|
|
61
|
+
hash = (hash << 5) + hash + stableString.charCodeAt(i);
|
|
62
|
+
hash = hash & hash;
|
|
63
|
+
}
|
|
64
|
+
const hashHex = (hash >>> 0).toString(16);
|
|
65
|
+
const timeBucket = Math.floor(Date.now() / (5 * 60 * 1e3));
|
|
66
|
+
return `reevit_${timeBucket}_${hashHex}`;
|
|
67
|
+
}
|
|
55
68
|
var ReevitAPIClient = class {
|
|
56
69
|
constructor(config) {
|
|
57
70
|
this.publicKey = config.publicKey || "";
|
|
@@ -60,20 +73,21 @@ var ReevitAPIClient = class {
|
|
|
60
73
|
}
|
|
61
74
|
/**
|
|
62
75
|
* Makes an authenticated API request
|
|
76
|
+
* @param idempotencyKey Optional deterministic idempotency key for the request
|
|
63
77
|
*/
|
|
64
|
-
async request(method, path, body) {
|
|
78
|
+
async request(method, path, body, idempotencyKey) {
|
|
65
79
|
const controller = new AbortController();
|
|
66
80
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
67
81
|
const headers = {
|
|
68
82
|
"Content-Type": "application/json",
|
|
69
83
|
"X-Reevit-Client": "@reevit/core",
|
|
70
|
-
"X-Reevit-Client-Version": "0.
|
|
84
|
+
"X-Reevit-Client-Version": "0.5.9"
|
|
71
85
|
};
|
|
72
86
|
if (this.publicKey) {
|
|
73
87
|
headers["X-Reevit-Key"] = this.publicKey;
|
|
74
88
|
}
|
|
75
89
|
if (method === "POST" || method === "PATCH" || method === "PUT") {
|
|
76
|
-
headers["Idempotency-Key"] = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}
|
|
90
|
+
headers["Idempotency-Key"] = idempotencyKey || (body ? generateIdempotencyKey(body) : `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`);
|
|
77
91
|
}
|
|
78
92
|
try {
|
|
79
93
|
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
@@ -145,7 +159,16 @@ var ReevitAPIClient = class {
|
|
|
145
159
|
allowed_providers: options?.allowedProviders
|
|
146
160
|
};
|
|
147
161
|
}
|
|
148
|
-
|
|
162
|
+
const idempotencyKey = generateIdempotencyKey({
|
|
163
|
+
amount: config.amount,
|
|
164
|
+
currency: config.currency,
|
|
165
|
+
customer: config.email || config.metadata?.customerId || "",
|
|
166
|
+
reference: config.reference || "",
|
|
167
|
+
method: method || "",
|
|
168
|
+
provider: options?.preferredProviders?.[0] || options?.allowedProviders?.[0] || "",
|
|
169
|
+
publicKey: this.publicKey
|
|
170
|
+
});
|
|
171
|
+
return this.request("POST", "/v1/payments/intents", request, idempotencyKey);
|
|
149
172
|
}
|
|
150
173
|
/**
|
|
151
174
|
* Retrieves a payment intent by ID
|
|
@@ -275,22 +298,29 @@ function detectNetwork(phone) {
|
|
|
275
298
|
function createThemeVariables(theme) {
|
|
276
299
|
const variables = {};
|
|
277
300
|
if (theme.primaryColor) {
|
|
278
|
-
variables["--reevit-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
301
|
+
variables["--reevit-text"] = theme.primaryColor;
|
|
302
|
+
}
|
|
303
|
+
if (theme.primaryForegroundColor) {
|
|
304
|
+
variables["--reevit-text-secondary"] = theme.primaryForegroundColor;
|
|
305
|
+
variables["--reevit-muted"] = theme.primaryForegroundColor;
|
|
306
|
+
}
|
|
307
|
+
if (theme.buttonBackgroundColor) {
|
|
308
|
+
variables["--reevit-primary"] = theme.buttonBackgroundColor;
|
|
309
|
+
variables["--reevit-primary-hover"] = theme.buttonBackgroundColor;
|
|
310
|
+
}
|
|
311
|
+
if (theme.buttonTextColor) {
|
|
312
|
+
variables["--reevit-primary-foreground"] = theme.buttonTextColor;
|
|
287
313
|
}
|
|
288
314
|
if (theme.backgroundColor) {
|
|
289
315
|
variables["--reevit-background"] = theme.backgroundColor;
|
|
316
|
+
variables["--reevit-surface"] = theme.backgroundColor;
|
|
290
317
|
}
|
|
291
318
|
if (theme.surfaceColor) {
|
|
292
319
|
variables["--reevit-surface"] = theme.surfaceColor;
|
|
293
320
|
}
|
|
321
|
+
if (theme.borderColor) {
|
|
322
|
+
variables["--reevit-border"] = theme.borderColor;
|
|
323
|
+
}
|
|
294
324
|
if (theme.textColor) {
|
|
295
325
|
variables["--reevit-text"] = theme.textColor;
|
|
296
326
|
}
|
|
@@ -307,24 +337,6 @@ function createThemeVariables(theme) {
|
|
|
307
337
|
}
|
|
308
338
|
return variables;
|
|
309
339
|
}
|
|
310
|
-
function getContrastingColor(color) {
|
|
311
|
-
const hex = color.trim();
|
|
312
|
-
if (!hex.startsWith("#")) {
|
|
313
|
-
return null;
|
|
314
|
-
}
|
|
315
|
-
const normalized = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
|
|
316
|
-
if (normalized.length !== 7) {
|
|
317
|
-
return null;
|
|
318
|
-
}
|
|
319
|
-
const r = parseInt(normalized.slice(1, 3), 16);
|
|
320
|
-
const g = parseInt(normalized.slice(3, 5), 16);
|
|
321
|
-
const b = parseInt(normalized.slice(5, 7), 16);
|
|
322
|
-
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
const brightness = (r * 299 + g * 587 + b * 114) / 1e3;
|
|
326
|
-
return brightness >= 140 ? "#0b1120" : "#ffffff";
|
|
327
|
-
}
|
|
328
340
|
function cn(...classes) {
|
|
329
341
|
return classes.filter(Boolean).join(" ");
|
|
330
342
|
}
|
|
@@ -395,6 +407,7 @@ function reevitReducer(state, action) {
|
|
|
395
407
|
detectNetwork,
|
|
396
408
|
formatAmount,
|
|
397
409
|
formatPhone,
|
|
410
|
+
generateIdempotencyKey,
|
|
398
411
|
generateReference,
|
|
399
412
|
reevitReducer,
|
|
400
413
|
validatePhone
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/api/client.ts","../src/utils.ts","../src/state.ts"],"sourcesContent":["/**\n * @reevit/core\n * Shared utilities and API client for Reevit payment SDKs\n */\n\n// API Client\nexport {\n ReevitAPIClient,\n createReevitClient,\n type ReevitAPIClientConfig,\n type CreatePaymentIntentRequest,\n type PaymentIntentResponse,\n type PaymentDetailResponse,\n type ConfirmPaymentRequest,\n type APIErrorResponse,\n} from './api/client';\n\n// Types\nexport type {\n PaymentMethod,\n MobileMoneyNetwork,\n ReevitCheckoutConfig,\n ReevitCheckoutCallbacks,\n CheckoutState,\n PaymentResult,\n PaymentError,\n ReevitTheme,\n CheckoutProviderOption,\n MobileMoneyFormData,\n CardFormData,\n PaymentIntent,\n PSPConfig,\n PSPType,\n PaymentSource,\n HubtelSessionResponse,\n} from './types';\n\n// Utilities\nexport {\n formatAmount,\n generateReference,\n validatePhone,\n formatPhone,\n detectNetwork,\n detectCountryFromCurrency,\n createThemeVariables,\n cn,\n} from './utils';\n\n// State machine helpers\nexport {\n createInitialState,\n reevitReducer,\n type ReevitState,\n type ReevitAction,\n} from './state';\n","/**\n * Reevit API Client\n * \n * Handles communication with the Reevit backend for payment operations.\n */\n\nimport type { PaymentMethod, ReevitCheckoutConfig, PaymentError, HubtelSessionResponse } from '../types';\n\n// API Response Types (matching backend handlers_payments.go)\nexport interface CreatePaymentIntentRequest {\n amount: number;\n currency: string;\n method?: string;\n country: string;\n customer_id?: string;\n metadata?: Record<string, unknown>;\n description?: string;\n policy?: {\n prefer?: string[];\n allowed_providers?: string[];\n max_amount?: number;\n blocked_bins?: string[];\n allowed_bins?: string[];\n velocity_max_per_minute?: number;\n };\n}\n\nexport interface PaymentIntentResponse {\n id: string;\n connection_id: string;\n provider: string;\n status: string;\n client_secret: string;\n psp_public_key: string;\n psp_credentials?: {\n merchantAccount?: string | number;\n basicAuth?: string;\n [key: string]: unknown;\n };\n amount: number;\n currency: string;\n fee_amount: number;\n fee_currency: string;\n net_amount: number;\n reference?: string;\n available_psps?: Array<{\n provider: string;\n name: string;\n methods: string[];\n countries?: string[];\n }>;\n branding?: Record<string, unknown>;\n}\n\nexport interface ConfirmPaymentRequest {\n provider_ref_id: string;\n provider_data?: Record<string, unknown>;\n}\n\nexport interface PaymentDetailResponse {\n id: string;\n connection_id: string;\n provider: string;\n method: string;\n status: string;\n amount: number;\n currency: string;\n fee_amount: number;\n fee_currency: string;\n net_amount: number;\n customer_id?: string;\n client_secret: string;\n provider_ref_id?: string;\n metadata?: Record<string, unknown>;\n created_at: string;\n updated_at: string;\n /** Payment source type (payment_link, api, subscription) */\n source?: 'payment_link' | 'api' | 'subscription';\n /** ID of the source (payment link ID, subscription ID, etc.) */\n source_id?: string;\n /** Human-readable description of the source (e.g., payment link name) */\n source_description?: string;\n}\n\nexport interface APIErrorResponse {\n code: string;\n message: string;\n details?: Record<string, string>;\n}\n\n// API Client configuration\nexport interface ReevitAPIClientConfig {\n /** Your Reevit public key */\n publicKey?: string;\n /** Base URL for the Reevit API (defaults to production) */\n baseUrl?: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n}\n\n// Default API base URLs\nconst API_BASE_URL_PRODUCTION = 'https://api.reevit.io';\nconst API_BASE_URL_SANDBOX = 'https://sandbox-api.reevit.io';\nconst DEFAULT_TIMEOUT = 30000; // 30 seconds\n\n/**\n * Determines if a public key is for sandbox mode\n */\nexport function isSandboxKey(publicKey: string): boolean {\n return publicKey.startsWith('pk_test_') ||\n publicKey.startsWith('pk_sandbox_') ||\n publicKey.startsWith('pfk_test_') ||\n publicKey.startsWith('pfk_sandbox_');\n}\n\n/**\n * Creates a PaymentError from an API error response\n */\nfunction createPaymentError(response: Response, errorData: APIErrorResponse): PaymentError {\n return {\n code: errorData.code || 'api_error',\n message: errorData.message || 'An unexpected error occurred',\n details: {\n httpStatus: response.status,\n ...errorData.details,\n },\n };\n}\n\n/**\n * Reevit API Client\n */\nexport class ReevitAPIClient {\n private readonly publicKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: ReevitAPIClientConfig) {\n this.publicKey = config.publicKey || '';\n this.baseUrl = config.baseUrl || (config.publicKey && isSandboxKey(config.publicKey)\n ? API_BASE_URL_SANDBOX\n : API_BASE_URL_PRODUCTION);\n this.timeout = config.timeout || DEFAULT_TIMEOUT;\n }\n\n /**\n * Makes an authenticated API request\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<{ data?: T; error?: PaymentError }> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n // Generate headers with idempotency key for mutating requests\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-Reevit-Client': '@reevit/core',\n 'X-Reevit-Client-Version': '0.3.2',\n };\n if (this.publicKey) {\n headers['X-Reevit-Key'] = this.publicKey;\n }\n\n if (method === 'POST' || method === 'PATCH' || method === 'PUT') {\n headers['Idempotency-Key'] = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;\n }\n\n try {\n const response = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const responseData = await response.json().catch(() => ({}));\n\n if (!response.ok) {\n return {\n error: createPaymentError(response, responseData as APIErrorResponse),\n };\n }\n\n return { data: responseData as T };\n } catch (err) {\n clearTimeout(timeoutId);\n\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return {\n error: {\n code: 'request_timeout',\n message: 'The request timed out. Please try again.',\n },\n };\n }\n\n if (err.message.includes('Failed to fetch') || err.message.includes('NetworkError')) {\n return {\n error: {\n code: 'network_error',\n message: 'Unable to connect to Reevit. Please check your internet connection.',\n },\n };\n }\n }\n\n return {\n error: {\n code: 'unknown_error',\n message: 'An unexpected error occurred. Please try again.',\n },\n };\n }\n }\n\n /**\n * Creates a payment intent\n */\n async createPaymentIntent(\n config: ReevitCheckoutConfig,\n method?: PaymentMethod,\n country: string = 'GH',\n options?: { preferredProviders?: string[]; allowedProviders?: string[] }\n ): Promise<{ data?: PaymentIntentResponse; error?: PaymentError }> {\n // Build metadata with customer_email for PSP providers that require it\n const metadata: Record<string, unknown> = { ...config.metadata };\n if (config.email) {\n metadata.customer_email = config.email;\n }\n if (config.phone) {\n metadata.customer_phone = config.phone;\n }\n\n const request: CreatePaymentIntentRequest = {\n amount: config.amount,\n currency: config.currency,\n country,\n customer_id: config.email || (config.metadata?.customerId as string | undefined),\n metadata,\n };\n\n if (method) {\n request.method = this.mapPaymentMethod(method);\n }\n\n if (options?.preferredProviders?.length || options?.allowedProviders?.length) {\n request.policy = {\n prefer: options?.preferredProviders,\n allowed_providers: options?.allowedProviders,\n };\n }\n\n return this.request<PaymentIntentResponse>('POST', '/v1/payments/intents', request);\n }\n\n /**\n * Retrieves a payment intent by ID\n */\n async getPaymentIntent(paymentId: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('GET', `/v1/payments/${paymentId}`);\n }\n\n /**\n * Confirms a payment after PSP callback\n */\n async confirmPayment(paymentId: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('POST', `/v1/payments/${paymentId}/confirm`);\n }\n\n /**\n * Confirms a payment intent using client secret (public endpoint)\n */\n async confirmPaymentIntent(paymentId: string, clientSecret: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('POST', `/v1/payments/${paymentId}/confirm-intent?client_secret=${clientSecret}`);\n }\n\n /**\n * Cancels a payment intent\n */\n async cancelPaymentIntent(paymentId: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('POST', `/v1/payments/${paymentId}/cancel`);\n }\n\n /**\n * Creates a Hubtel session token for secure checkout\n * Returns a short-lived token that contains Hubtel credentials\n * Credentials are never exposed to the client directly\n */\n async createHubtelSession(\n paymentId: string,\n clientSecret?: string\n ): Promise<{ data?: HubtelSessionResponse; error?: PaymentError }> {\n const query = clientSecret ? `?client_secret=${encodeURIComponent(clientSecret)}` : '';\n return this.request<HubtelSessionResponse>('POST', `/v1/payments/hubtel/sessions/${paymentId}${query}`);\n }\n\n /**\n * Maps SDK payment method to backend format\n */\n private mapPaymentMethod(method: PaymentMethod): string {\n switch (method) {\n case 'card':\n return 'card';\n case 'mobile_money':\n return 'mobile_money';\n case 'bank_transfer':\n return 'bank_transfer';\n default:\n return method;\n }\n }\n}\n\n/**\n * Creates a new Reevit API client instance\n */\nexport function createReevitClient(config: ReevitAPIClientConfig): ReevitAPIClient {\n return new ReevitAPIClient(config);\n}\n","/**\n * Utility Functions\n * Shared utilities for Reevit SDKs\n */\n\nimport type { MobileMoneyNetwork, ReevitTheme } from './types';\n\n/**\n * Formats an amount from smallest currency unit to display format\n */\nexport function formatAmount(amount: number, currency: string): string {\n const majorUnit = amount / 100;\n\n const currencyFormats: Record<string, { locale: string; minimumFractionDigits: number }> = {\n GHS: { locale: 'en-GH', minimumFractionDigits: 2 },\n NGN: { locale: 'en-NG', minimumFractionDigits: 2 },\n KES: { locale: 'en-KE', minimumFractionDigits: 2 },\n USD: { locale: 'en-US', minimumFractionDigits: 2 },\n EUR: { locale: 'de-DE', minimumFractionDigits: 2 },\n GBP: { locale: 'en-GB', minimumFractionDigits: 2 },\n };\n\n const format = currencyFormats[currency.toUpperCase()] || { locale: 'en-US', minimumFractionDigits: 2 };\n\n try {\n return new Intl.NumberFormat(format.locale, {\n style: 'currency',\n currency: currency.toUpperCase(),\n minimumFractionDigits: format.minimumFractionDigits,\n }).format(majorUnit);\n } catch {\n // Fallback for unsupported currencies\n return `${currency} ${majorUnit.toFixed(2)}`;\n }\n}\n\n/**\n * Generates a unique payment reference\n */\nexport function generateReference(prefix: string = 'reevit'): string {\n const timestamp = Date.now().toString(36);\n const random = Math.random().toString(36).substring(2, 8);\n return `${prefix}_${timestamp}_${random}`;\n}\n\n/**\n * Validates a phone number for mobile money\n */\nexport function validatePhone(phone: string, country: string = 'GH'): boolean {\n // Remove non-digit characters\n const digits = phone.replace(/\\D/g, '');\n\n const patterns: Record<string, RegExp> = {\n GH: /^(?:233|0)?[235][0-9]{8}$/, // Ghana\n NG: /^(?:234|0)?[789][01][0-9]{8}$/, // Nigeria\n KE: /^(?:254|0)?[17][0-9]{8}$/, // Kenya\n };\n\n const pattern = patterns[country.toUpperCase()];\n if (!pattern) return digits.length >= 10;\n\n return pattern.test(digits);\n}\n\n/**\n * Formats a phone number for display\n */\nexport function formatPhone(phone: string, country: string = 'GH'): string {\n const digits = phone.replace(/\\D/g, '');\n\n if (country === 'GH') {\n // Format as 0XX XXX XXXX\n if (digits.startsWith('233') && digits.length === 12) {\n const local = '0' + digits.slice(3);\n return `${local.slice(0, 3)} ${local.slice(3, 6)} ${local.slice(6)}`;\n }\n if (digits.length === 10 && digits.startsWith('0')) {\n return `${digits.slice(0, 3)} ${digits.slice(3, 6)} ${digits.slice(6)}`;\n }\n }\n\n return phone;\n}\n\n/**\n * Detects mobile money network from phone number (Ghana)\n */\nexport function detectNetwork(phone: string): MobileMoneyNetwork | null {\n const digits = phone.replace(/\\D/g, '');\n\n // Get the network prefix (first 3 digits after country code or 0)\n let prefix: string;\n if (digits.startsWith('233')) {\n prefix = digits.slice(3, 5);\n } else if (digits.startsWith('0')) {\n prefix = digits.slice(1, 3);\n } else {\n prefix = digits.slice(0, 2);\n }\n\n // Ghana network prefixes\n const mtnPrefixes = ['24', '25', '53', '54', '55', '59'];\n const telecelPrefixes = ['20', '50'];\n const airteltigoPrefixes = ['26', '27', '56', '57'];\n\n if (mtnPrefixes.includes(prefix)) return 'mtn';\n if (telecelPrefixes.includes(prefix)) return 'telecel';\n if (airteltigoPrefixes.includes(prefix)) return 'airteltigo';\n\n return null;\n}\n\n/**\n * Creates CSS custom property variables from theme\n */\nexport function createThemeVariables(theme: ReevitTheme): Record<string, string> {\n const variables: Record<string, string> = {};\n\n if (theme.primaryColor) {\n variables['--reevit-primary'] = theme.primaryColor;\n if (theme.primaryForegroundColor) {\n variables['--reevit-primary-foreground'] = theme.primaryForegroundColor;\n } else {\n const contrast = getContrastingColor(theme.primaryColor);\n if (contrast) {\n variables['--reevit-primary-foreground'] = contrast;\n }\n }\n }\n if (theme.backgroundColor) {\n variables['--reevit-background'] = theme.backgroundColor;\n }\n if (theme.surfaceColor) {\n variables['--reevit-surface'] = theme.surfaceColor;\n }\n if (theme.textColor) {\n variables['--reevit-text'] = theme.textColor;\n }\n if (theme.mutedTextColor) {\n variables['--reevit-text-secondary'] = theme.mutedTextColor;\n }\n if (theme.borderRadius) {\n variables['--reevit-radius'] = theme.borderRadius;\n variables['--reevit-radius-sm'] = theme.borderRadius;\n variables['--reevit-radius-lg'] = theme.borderRadius;\n }\n if (theme.fontFamily) {\n variables['--reevit-font'] = theme.fontFamily;\n }\n\n return variables;\n}\n\nfunction getContrastingColor(color: string): string | null {\n const hex = color.trim();\n if (!hex.startsWith('#')) {\n return null;\n }\n\n const normalized = hex.length === 4\n ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`\n : hex;\n\n if (normalized.length !== 7) {\n return null;\n }\n\n const r = parseInt(normalized.slice(1, 3), 16);\n const g = parseInt(normalized.slice(3, 5), 16);\n const b = parseInt(normalized.slice(5, 7), 16);\n\n if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {\n return null;\n }\n\n const brightness = (r * 299 + g * 587 + b * 114) / 1000;\n return brightness >= 140 ? '#0b1120' : '#ffffff';\n}\n\n/**\n * Simple class name concatenation utility\n */\nexport function cn(...classes: (string | boolean | undefined | null)[]): string {\n return classes.filter(Boolean).join(' ');\n}\n\n/**\n * Detects country code from currency\n */\nexport function detectCountryFromCurrency(currency: string): string {\n const currencyToCountry: Record<string, string> = {\n GHS: 'GH',\n NGN: 'NG',\n KES: 'KE',\n UGX: 'UG',\n TZS: 'TZ',\n ZAR: 'ZA',\n XOF: 'CI',\n XAF: 'CM',\n USD: 'US',\n EUR: 'DE',\n GBP: 'GB',\n };\n\n return currencyToCountry[currency.toUpperCase()] || 'GH';\n}\n","/**\n * Reevit State Machine\n * Shared state management logic for all SDKs\n */\n\nimport type { CheckoutState, PaymentIntent, PaymentMethod, PaymentResult, PaymentError } from './types';\n\n// State shape\nexport interface ReevitState {\n status: CheckoutState;\n paymentIntent: PaymentIntent | null;\n selectedMethod: PaymentMethod | null;\n error: PaymentError | null;\n result: PaymentResult | null;\n}\n\n// Actions\nexport type ReevitAction =\n | { type: 'INIT_START' }\n | { type: 'INIT_SUCCESS'; payload: PaymentIntent }\n | { type: 'INIT_ERROR'; payload: PaymentError }\n | { type: 'SELECT_METHOD'; payload: PaymentMethod }\n | { type: 'PROCESS_START' }\n | { type: 'PROCESS_SUCCESS'; payload: PaymentResult }\n | { type: 'PROCESS_ERROR'; payload: PaymentError }\n | { type: 'RESET' }\n | { type: 'CLOSE' };\n\n/**\n * Creates the initial state for the checkout\n */\nexport function createInitialState(): ReevitState {\n return {\n status: 'idle',\n paymentIntent: null,\n selectedMethod: null,\n error: null,\n result: null,\n };\n}\n\n/**\n * State reducer for checkout flow\n */\nexport function reevitReducer(state: ReevitState, action: ReevitAction): ReevitState {\n switch (action.type) {\n case 'INIT_START':\n return { ...state, status: 'loading', error: null };\n case 'INIT_SUCCESS':\n return {\n ...state,\n status: 'ready',\n paymentIntent: action.payload,\n selectedMethod:\n action.payload.availableMethods?.length === 1 ? action.payload.availableMethods[0] : null,\n };\n case 'INIT_ERROR':\n return { ...state, status: 'failed', error: action.payload };\n case 'SELECT_METHOD':\n return { ...state, status: 'method_selected', selectedMethod: action.payload };\n case 'PROCESS_START':\n return { ...state, status: 'processing', error: null };\n case 'PROCESS_SUCCESS':\n return { ...state, status: 'success', result: action.payload };\n case 'PROCESS_ERROR':\n return { ...state, status: 'failed', error: action.payload };\n case 'RESET':\n return { ...createInitialState(), status: 'ready', paymentIntent: state.paymentIntent };\n case 'CLOSE':\n return { ...state, status: 'closed' };\n default:\n return state;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/api/client.ts","../src/utils.ts","../src/state.ts"],"sourcesContent":["/**\n * @reevit/core\n * Shared utilities and API client for Reevit payment SDKs\n */\n\n// API Client\nexport {\n ReevitAPIClient,\n createReevitClient,\n generateIdempotencyKey,\n type ReevitAPIClientConfig,\n type CreatePaymentIntentRequest,\n type PaymentIntentResponse,\n type PaymentDetailResponse,\n type ConfirmPaymentRequest,\n type APIErrorResponse,\n} from './api/client';\n\n// Types\nexport type {\n PaymentMethod,\n MobileMoneyNetwork,\n ReevitCheckoutConfig,\n ReevitCheckoutCallbacks,\n CheckoutState,\n PaymentResult,\n PaymentError,\n ReevitTheme,\n CheckoutProviderOption,\n MobileMoneyFormData,\n CardFormData,\n PaymentIntent,\n PSPConfig,\n PSPType,\n PaymentSource,\n HubtelSessionResponse,\n} from './types';\n\n// Utilities\nexport {\n formatAmount,\n generateReference,\n validatePhone,\n formatPhone,\n detectNetwork,\n detectCountryFromCurrency,\n createThemeVariables,\n cn,\n} from './utils';\n\n// State machine helpers\nexport {\n createInitialState,\n reevitReducer,\n type ReevitState,\n type ReevitAction,\n} from './state';\n","/**\n * Reevit API Client\n * \n * Handles communication with the Reevit backend for payment operations.\n */\n\nimport type { PaymentMethod, ReevitCheckoutConfig, PaymentError, HubtelSessionResponse } from '../types';\n\n// API Response Types (matching backend handlers_payments.go)\nexport interface CreatePaymentIntentRequest {\n amount: number;\n currency: string;\n method?: string;\n country: string;\n customer_id?: string;\n metadata?: Record<string, unknown>;\n description?: string;\n policy?: {\n prefer?: string[];\n allowed_providers?: string[];\n max_amount?: number;\n blocked_bins?: string[];\n allowed_bins?: string[];\n velocity_max_per_minute?: number;\n };\n}\n\nexport interface PaymentIntentResponse {\n id: string;\n org_id?: string;\n connection_id: string;\n provider: string;\n status: string;\n client_secret: string;\n psp_public_key: string;\n psp_credentials?: {\n merchantAccount?: string | number;\n basicAuth?: string;\n [key: string]: unknown;\n };\n amount: number;\n currency: string;\n fee_amount: number;\n fee_currency: string;\n net_amount: number;\n reference?: string;\n available_psps?: Array<{\n provider: string;\n name: string;\n methods: string[];\n countries?: string[];\n }>;\n branding?: Record<string, unknown>;\n}\n\nexport interface ConfirmPaymentRequest {\n provider_ref_id: string;\n provider_data?: Record<string, unknown>;\n}\n\nexport interface PaymentDetailResponse {\n id: string;\n connection_id: string;\n provider: string;\n method: string;\n status: string;\n amount: number;\n currency: string;\n fee_amount: number;\n fee_currency: string;\n net_amount: number;\n customer_id?: string;\n client_secret: string;\n provider_ref_id?: string;\n metadata?: Record<string, unknown>;\n created_at: string;\n updated_at: string;\n /** Payment source type (payment_link, api, subscription) */\n source?: 'payment_link' | 'api' | 'subscription';\n /** ID of the source (payment link ID, subscription ID, etc.) */\n source_id?: string;\n /** Human-readable description of the source (e.g., payment link name) */\n source_description?: string;\n}\n\nexport interface APIErrorResponse {\n code: string;\n message: string;\n details?: Record<string, string>;\n}\n\n// API Client configuration\nexport interface ReevitAPIClientConfig {\n /** Your Reevit public key */\n publicKey?: string;\n /** Base URL for the Reevit API (defaults to production) */\n baseUrl?: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n}\n\n// Default API base URLs\nconst API_BASE_URL_PRODUCTION = 'https://api.reevit.io';\nconst API_BASE_URL_SANDBOX = 'https://sandbox-api.reevit.io';\nconst DEFAULT_TIMEOUT = 30000; // 30 seconds\n\n/**\n * Determines if a public key is for sandbox mode\n */\nexport function isSandboxKey(publicKey: string): boolean {\n return publicKey.startsWith('pk_test_') ||\n publicKey.startsWith('pk_sandbox_') ||\n publicKey.startsWith('pfk_test_') ||\n publicKey.startsWith('pfk_sandbox_');\n}\n\n/**\n * Creates a PaymentError from an API error response\n */\nfunction createPaymentError(response: Response, errorData: APIErrorResponse): PaymentError {\n return {\n code: errorData.code || 'api_error',\n message: errorData.message || 'An unexpected error occurred',\n details: {\n httpStatus: response.status,\n ...errorData.details,\n },\n };\n}\n\n/**\n * Generates a deterministic idempotency key based on input parameters\n * Uses a simple hash function suitable for browser environments\n * Exported for use by SDK hooks (e.g., payment link flows)\n */\nexport function generateIdempotencyKey(params: Record<string, unknown>): string {\n // Create a stable string representation of the parameters\n const sortedKeys = Object.keys(params).sort();\n const stableString = sortedKeys\n .map(key => `${key}:${JSON.stringify(params[key])}`)\n .join('|');\n\n // Simple hash function (djb2 algorithm)\n let hash = 5381;\n for (let i = 0; i < stableString.length; i++) {\n hash = ((hash << 5) + hash) + stableString.charCodeAt(i);\n hash = hash & hash; // Convert to 32-bit integer\n }\n\n // Convert to positive hex string\n const hashHex = (hash >>> 0).toString(16);\n\n // Add a time bucket (5-minute windows) to allow retries within a reasonable window\n // but prevent keys from being reused across completely different sessions\n const timeBucket = Math.floor(Date.now() / (5 * 60 * 1000));\n\n return `reevit_${timeBucket}_${hashHex}`;\n}\n\n/**\n * Reevit API Client\n */\nexport class ReevitAPIClient {\n private readonly publicKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: ReevitAPIClientConfig) {\n this.publicKey = config.publicKey || '';\n this.baseUrl = config.baseUrl || (config.publicKey && isSandboxKey(config.publicKey)\n ? API_BASE_URL_SANDBOX\n : API_BASE_URL_PRODUCTION);\n this.timeout = config.timeout || DEFAULT_TIMEOUT;\n }\n\n /**\n * Makes an authenticated API request\n * @param idempotencyKey Optional deterministic idempotency key for the request\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n idempotencyKey?: string\n ): Promise<{ data?: T; error?: PaymentError }> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n // Generate headers with idempotency key for mutating requests\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-Reevit-Client': '@reevit/core',\n 'X-Reevit-Client-Version': '0.5.9',\n };\n if (this.publicKey) {\n headers['X-Reevit-Key'] = this.publicKey;\n }\n\n if (method === 'POST' || method === 'PATCH' || method === 'PUT') {\n // Use provided deterministic key, or generate one based on request body\n headers['Idempotency-Key'] = idempotencyKey ||\n (body ? generateIdempotencyKey(body as Record<string, unknown>) : `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`);\n }\n\n try {\n const response = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const responseData = await response.json().catch(() => ({}));\n\n if (!response.ok) {\n return {\n error: createPaymentError(response, responseData as APIErrorResponse),\n };\n }\n\n return { data: responseData as T };\n } catch (err) {\n clearTimeout(timeoutId);\n\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return {\n error: {\n code: 'request_timeout',\n message: 'The request timed out. Please try again.',\n },\n };\n }\n\n if (err.message.includes('Failed to fetch') || err.message.includes('NetworkError')) {\n return {\n error: {\n code: 'network_error',\n message: 'Unable to connect to Reevit. Please check your internet connection.',\n },\n };\n }\n }\n\n return {\n error: {\n code: 'unknown_error',\n message: 'An unexpected error occurred. Please try again.',\n },\n };\n }\n }\n\n /**\n * Creates a payment intent\n */\n async createPaymentIntent(\n config: ReevitCheckoutConfig,\n method?: PaymentMethod,\n country: string = 'GH',\n options?: { preferredProviders?: string[]; allowedProviders?: string[] }\n ): Promise<{ data?: PaymentIntentResponse; error?: PaymentError }> {\n // Build metadata with customer_email for PSP providers that require it\n const metadata: Record<string, unknown> = { ...config.metadata };\n if (config.email) {\n metadata.customer_email = config.email;\n }\n if (config.phone) {\n metadata.customer_phone = config.phone;\n }\n\n const request: CreatePaymentIntentRequest = {\n amount: config.amount,\n currency: config.currency,\n country,\n customer_id: config.email || (config.metadata?.customerId as string | undefined),\n metadata,\n };\n\n if (method) {\n request.method = this.mapPaymentMethod(method);\n }\n\n if (options?.preferredProviders?.length || options?.allowedProviders?.length) {\n request.policy = {\n prefer: options?.preferredProviders,\n allowed_providers: options?.allowedProviders,\n };\n }\n\n // Generate a deterministic idempotency key based on payment parameters\n // This ensures that duplicate requests for the same payment return the same intent\n const idempotencyKey = generateIdempotencyKey({\n amount: config.amount,\n currency: config.currency,\n customer: config.email || config.metadata?.customerId || '',\n reference: config.reference || '',\n method: method || '',\n provider: options?.preferredProviders?.[0] || options?.allowedProviders?.[0] || '',\n publicKey: this.publicKey,\n });\n\n return this.request<PaymentIntentResponse>('POST', '/v1/payments/intents', request, idempotencyKey);\n }\n\n /**\n * Retrieves a payment intent by ID\n */\n async getPaymentIntent(paymentId: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('GET', `/v1/payments/${paymentId}`);\n }\n\n /**\n * Confirms a payment after PSP callback\n */\n async confirmPayment(paymentId: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('POST', `/v1/payments/${paymentId}/confirm`);\n }\n\n /**\n * Confirms a payment intent using client secret (public endpoint)\n */\n async confirmPaymentIntent(paymentId: string, clientSecret: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('POST', `/v1/payments/${paymentId}/confirm-intent?client_secret=${clientSecret}`);\n }\n\n /**\n * Cancels a payment intent\n */\n async cancelPaymentIntent(paymentId: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('POST', `/v1/payments/${paymentId}/cancel`);\n }\n\n /**\n * Creates a Hubtel session token for secure checkout\n * Returns a short-lived token that contains Hubtel credentials\n * Credentials are never exposed to the client directly\n */\n async createHubtelSession(\n paymentId: string,\n clientSecret?: string\n ): Promise<{ data?: HubtelSessionResponse; error?: PaymentError }> {\n const query = clientSecret ? `?client_secret=${encodeURIComponent(clientSecret)}` : '';\n return this.request<HubtelSessionResponse>('POST', `/v1/payments/hubtel/sessions/${paymentId}${query}`);\n }\n\n /**\n * Maps SDK payment method to backend format\n */\n private mapPaymentMethod(method: PaymentMethod): string {\n switch (method) {\n case 'card':\n return 'card';\n case 'mobile_money':\n return 'mobile_money';\n case 'bank_transfer':\n return 'bank_transfer';\n default:\n return method;\n }\n }\n}\n\n/**\n * Creates a new Reevit API client instance\n */\nexport function createReevitClient(config: ReevitAPIClientConfig): ReevitAPIClient {\n return new ReevitAPIClient(config);\n}\n","/**\n * Utility Functions\n * Shared utilities for Reevit SDKs\n */\n\nimport type { MobileMoneyNetwork, ReevitTheme } from './types';\n\n/**\n * Formats an amount from smallest currency unit to display format\n */\nexport function formatAmount(amount: number, currency: string): string {\n const majorUnit = amount / 100;\n\n const currencyFormats: Record<string, { locale: string; minimumFractionDigits: number }> = {\n GHS: { locale: 'en-GH', minimumFractionDigits: 2 },\n NGN: { locale: 'en-NG', minimumFractionDigits: 2 },\n KES: { locale: 'en-KE', minimumFractionDigits: 2 },\n USD: { locale: 'en-US', minimumFractionDigits: 2 },\n EUR: { locale: 'de-DE', minimumFractionDigits: 2 },\n GBP: { locale: 'en-GB', minimumFractionDigits: 2 },\n };\n\n const format = currencyFormats[currency.toUpperCase()] || { locale: 'en-US', minimumFractionDigits: 2 };\n\n try {\n return new Intl.NumberFormat(format.locale, {\n style: 'currency',\n currency: currency.toUpperCase(),\n minimumFractionDigits: format.minimumFractionDigits,\n }).format(majorUnit);\n } catch {\n // Fallback for unsupported currencies\n return `${currency} ${majorUnit.toFixed(2)}`;\n }\n}\n\n/**\n * Generates a unique payment reference\n */\nexport function generateReference(prefix: string = 'reevit'): string {\n const timestamp = Date.now().toString(36);\n const random = Math.random().toString(36).substring(2, 8);\n return `${prefix}_${timestamp}_${random}`;\n}\n\n/**\n * Validates a phone number for mobile money\n */\nexport function validatePhone(phone: string, country: string = 'GH'): boolean {\n // Remove non-digit characters\n const digits = phone.replace(/\\D/g, '');\n\n const patterns: Record<string, RegExp> = {\n GH: /^(?:233|0)?[235][0-9]{8}$/, // Ghana\n NG: /^(?:234|0)?[789][01][0-9]{8}$/, // Nigeria\n KE: /^(?:254|0)?[17][0-9]{8}$/, // Kenya\n };\n\n const pattern = patterns[country.toUpperCase()];\n if (!pattern) return digits.length >= 10;\n\n return pattern.test(digits);\n}\n\n/**\n * Formats a phone number for display\n */\nexport function formatPhone(phone: string, country: string = 'GH'): string {\n const digits = phone.replace(/\\D/g, '');\n\n if (country === 'GH') {\n // Format as 0XX XXX XXXX\n if (digits.startsWith('233') && digits.length === 12) {\n const local = '0' + digits.slice(3);\n return `${local.slice(0, 3)} ${local.slice(3, 6)} ${local.slice(6)}`;\n }\n if (digits.length === 10 && digits.startsWith('0')) {\n return `${digits.slice(0, 3)} ${digits.slice(3, 6)} ${digits.slice(6)}`;\n }\n }\n\n return phone;\n}\n\n/**\n * Detects mobile money network from phone number (Ghana)\n */\nexport function detectNetwork(phone: string): MobileMoneyNetwork | null {\n const digits = phone.replace(/\\D/g, '');\n\n // Get the network prefix (first 3 digits after country code or 0)\n let prefix: string;\n if (digits.startsWith('233')) {\n prefix = digits.slice(3, 5);\n } else if (digits.startsWith('0')) {\n prefix = digits.slice(1, 3);\n } else {\n prefix = digits.slice(0, 2);\n }\n\n // Ghana network prefixes\n const mtnPrefixes = ['24', '25', '53', '54', '55', '59'];\n const telecelPrefixes = ['20', '50'];\n const airteltigoPrefixes = ['26', '27', '56', '57'];\n\n if (mtnPrefixes.includes(prefix)) return 'mtn';\n if (telecelPrefixes.includes(prefix)) return 'telecel';\n if (airteltigoPrefixes.includes(prefix)) return 'airteltigo';\n\n return null;\n}\n\n/**\n * Creates CSS custom property variables from theme\n */\nexport function createThemeVariables(theme: ReevitTheme): Record<string, string> {\n const variables: Record<string, string> = {};\n\n // Primary color = main text color\n if (theme.primaryColor) {\n variables['--reevit-text'] = theme.primaryColor;\n }\n\n // Primary foreground = description/secondary text color\n if (theme.primaryForegroundColor) {\n variables['--reevit-text-secondary'] = theme.primaryForegroundColor;\n variables['--reevit-muted'] = theme.primaryForegroundColor;\n }\n\n // Button colors\n if (theme.buttonBackgroundColor) {\n variables['--reevit-primary'] = theme.buttonBackgroundColor;\n variables['--reevit-primary-hover'] = theme.buttonBackgroundColor;\n }\n if (theme.buttonTextColor) {\n variables['--reevit-primary-foreground'] = theme.buttonTextColor;\n }\n\n // Background and surface colors\n if (theme.backgroundColor) {\n variables['--reevit-background'] = theme.backgroundColor;\n variables['--reevit-surface'] = theme.backgroundColor;\n }\n if (theme.surfaceColor) {\n variables['--reevit-surface'] = theme.surfaceColor;\n }\n\n // Border color\n if (theme.borderColor) {\n variables['--reevit-border'] = theme.borderColor;\n }\n\n // Legacy text color support\n if (theme.textColor) {\n variables['--reevit-text'] = theme.textColor;\n }\n if (theme.mutedTextColor) {\n variables['--reevit-text-secondary'] = theme.mutedTextColor;\n }\n\n // Border radius\n if (theme.borderRadius) {\n variables['--reevit-radius'] = theme.borderRadius;\n variables['--reevit-radius-sm'] = theme.borderRadius;\n variables['--reevit-radius-lg'] = theme.borderRadius;\n }\n\n // Font family\n if (theme.fontFamily) {\n variables['--reevit-font'] = theme.fontFamily;\n }\n\n return variables;\n}\n\nfunction getContrastingColor(color: string): string | null {\n const hex = color.trim();\n if (!hex.startsWith('#')) {\n return null;\n }\n\n const normalized = hex.length === 4\n ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`\n : hex;\n\n if (normalized.length !== 7) {\n return null;\n }\n\n const r = parseInt(normalized.slice(1, 3), 16);\n const g = parseInt(normalized.slice(3, 5), 16);\n const b = parseInt(normalized.slice(5, 7), 16);\n\n if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {\n return null;\n }\n\n const brightness = (r * 299 + g * 587 + b * 114) / 1000;\n return brightness >= 140 ? '#0b1120' : '#ffffff';\n}\n\n/**\n * Simple class name concatenation utility\n */\nexport function cn(...classes: (string | boolean | undefined | null)[]): string {\n return classes.filter(Boolean).join(' ');\n}\n\n/**\n * Detects country code from currency\n */\nexport function detectCountryFromCurrency(currency: string): string {\n const currencyToCountry: Record<string, string> = {\n GHS: 'GH',\n NGN: 'NG',\n KES: 'KE',\n UGX: 'UG',\n TZS: 'TZ',\n ZAR: 'ZA',\n XOF: 'CI',\n XAF: 'CM',\n USD: 'US',\n EUR: 'DE',\n GBP: 'GB',\n };\n\n return currencyToCountry[currency.toUpperCase()] || 'GH';\n}\n","/**\n * Reevit State Machine\n * Shared state management logic for all SDKs\n */\n\nimport type { CheckoutState, PaymentIntent, PaymentMethod, PaymentResult, PaymentError } from './types';\n\n// State shape\nexport interface ReevitState {\n status: CheckoutState;\n paymentIntent: PaymentIntent | null;\n selectedMethod: PaymentMethod | null;\n error: PaymentError | null;\n result: PaymentResult | null;\n}\n\n// Actions\nexport type ReevitAction =\n | { type: 'INIT_START' }\n | { type: 'INIT_SUCCESS'; payload: PaymentIntent }\n | { type: 'INIT_ERROR'; payload: PaymentError }\n | { type: 'SELECT_METHOD'; payload: PaymentMethod }\n | { type: 'PROCESS_START' }\n | { type: 'PROCESS_SUCCESS'; payload: PaymentResult }\n | { type: 'PROCESS_ERROR'; payload: PaymentError }\n | { type: 'RESET' }\n | { type: 'CLOSE' };\n\n/**\n * Creates the initial state for the checkout\n */\nexport function createInitialState(): ReevitState {\n return {\n status: 'idle',\n paymentIntent: null,\n selectedMethod: null,\n error: null,\n result: null,\n };\n}\n\n/**\n * State reducer for checkout flow\n */\nexport function reevitReducer(state: ReevitState, action: ReevitAction): ReevitState {\n switch (action.type) {\n case 'INIT_START':\n return { ...state, status: 'loading', error: null };\n case 'INIT_SUCCESS':\n return {\n ...state,\n status: 'ready',\n paymentIntent: action.payload,\n selectedMethod:\n action.payload.availableMethods?.length === 1 ? action.payload.availableMethods[0] : null,\n };\n case 'INIT_ERROR':\n return { ...state, status: 'failed', error: action.payload };\n case 'SELECT_METHOD':\n return { ...state, status: 'method_selected', selectedMethod: action.payload };\n case 'PROCESS_START':\n return { ...state, status: 'processing', error: null };\n case 'PROCESS_SUCCESS':\n return { ...state, status: 'success', result: action.payload };\n case 'PROCESS_ERROR':\n return { ...state, status: 'failed', error: action.payload };\n case 'RESET':\n return { ...createInitialState(), status: 'ready', paymentIntent: state.paymentIntent };\n case 'CLOSE':\n return { ...state, status: 'closed' };\n default:\n return state;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -15,6 +15,18 @@ function createPaymentError(response, errorData) {
|
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
|
+
function generateIdempotencyKey(params) {
|
|
19
|
+
const sortedKeys = Object.keys(params).sort();
|
|
20
|
+
const stableString = sortedKeys.map((key) => `${key}:${JSON.stringify(params[key])}`).join("|");
|
|
21
|
+
let hash = 5381;
|
|
22
|
+
for (let i = 0; i < stableString.length; i++) {
|
|
23
|
+
hash = (hash << 5) + hash + stableString.charCodeAt(i);
|
|
24
|
+
hash = hash & hash;
|
|
25
|
+
}
|
|
26
|
+
const hashHex = (hash >>> 0).toString(16);
|
|
27
|
+
const timeBucket = Math.floor(Date.now() / (5 * 60 * 1e3));
|
|
28
|
+
return `reevit_${timeBucket}_${hashHex}`;
|
|
29
|
+
}
|
|
18
30
|
var ReevitAPIClient = class {
|
|
19
31
|
constructor(config) {
|
|
20
32
|
this.publicKey = config.publicKey || "";
|
|
@@ -23,20 +35,21 @@ var ReevitAPIClient = class {
|
|
|
23
35
|
}
|
|
24
36
|
/**
|
|
25
37
|
* Makes an authenticated API request
|
|
38
|
+
* @param idempotencyKey Optional deterministic idempotency key for the request
|
|
26
39
|
*/
|
|
27
|
-
async request(method, path, body) {
|
|
40
|
+
async request(method, path, body, idempotencyKey) {
|
|
28
41
|
const controller = new AbortController();
|
|
29
42
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
30
43
|
const headers = {
|
|
31
44
|
"Content-Type": "application/json",
|
|
32
45
|
"X-Reevit-Client": "@reevit/core",
|
|
33
|
-
"X-Reevit-Client-Version": "0.
|
|
46
|
+
"X-Reevit-Client-Version": "0.5.9"
|
|
34
47
|
};
|
|
35
48
|
if (this.publicKey) {
|
|
36
49
|
headers["X-Reevit-Key"] = this.publicKey;
|
|
37
50
|
}
|
|
38
51
|
if (method === "POST" || method === "PATCH" || method === "PUT") {
|
|
39
|
-
headers["Idempotency-Key"] = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}
|
|
52
|
+
headers["Idempotency-Key"] = idempotencyKey || (body ? generateIdempotencyKey(body) : `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`);
|
|
40
53
|
}
|
|
41
54
|
try {
|
|
42
55
|
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
@@ -108,7 +121,16 @@ var ReevitAPIClient = class {
|
|
|
108
121
|
allowed_providers: options?.allowedProviders
|
|
109
122
|
};
|
|
110
123
|
}
|
|
111
|
-
|
|
124
|
+
const idempotencyKey = generateIdempotencyKey({
|
|
125
|
+
amount: config.amount,
|
|
126
|
+
currency: config.currency,
|
|
127
|
+
customer: config.email || config.metadata?.customerId || "",
|
|
128
|
+
reference: config.reference || "",
|
|
129
|
+
method: method || "",
|
|
130
|
+
provider: options?.preferredProviders?.[0] || options?.allowedProviders?.[0] || "",
|
|
131
|
+
publicKey: this.publicKey
|
|
132
|
+
});
|
|
133
|
+
return this.request("POST", "/v1/payments/intents", request, idempotencyKey);
|
|
112
134
|
}
|
|
113
135
|
/**
|
|
114
136
|
* Retrieves a payment intent by ID
|
|
@@ -238,22 +260,29 @@ function detectNetwork(phone) {
|
|
|
238
260
|
function createThemeVariables(theme) {
|
|
239
261
|
const variables = {};
|
|
240
262
|
if (theme.primaryColor) {
|
|
241
|
-
variables["--reevit-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
263
|
+
variables["--reevit-text"] = theme.primaryColor;
|
|
264
|
+
}
|
|
265
|
+
if (theme.primaryForegroundColor) {
|
|
266
|
+
variables["--reevit-text-secondary"] = theme.primaryForegroundColor;
|
|
267
|
+
variables["--reevit-muted"] = theme.primaryForegroundColor;
|
|
268
|
+
}
|
|
269
|
+
if (theme.buttonBackgroundColor) {
|
|
270
|
+
variables["--reevit-primary"] = theme.buttonBackgroundColor;
|
|
271
|
+
variables["--reevit-primary-hover"] = theme.buttonBackgroundColor;
|
|
272
|
+
}
|
|
273
|
+
if (theme.buttonTextColor) {
|
|
274
|
+
variables["--reevit-primary-foreground"] = theme.buttonTextColor;
|
|
250
275
|
}
|
|
251
276
|
if (theme.backgroundColor) {
|
|
252
277
|
variables["--reevit-background"] = theme.backgroundColor;
|
|
278
|
+
variables["--reevit-surface"] = theme.backgroundColor;
|
|
253
279
|
}
|
|
254
280
|
if (theme.surfaceColor) {
|
|
255
281
|
variables["--reevit-surface"] = theme.surfaceColor;
|
|
256
282
|
}
|
|
283
|
+
if (theme.borderColor) {
|
|
284
|
+
variables["--reevit-border"] = theme.borderColor;
|
|
285
|
+
}
|
|
257
286
|
if (theme.textColor) {
|
|
258
287
|
variables["--reevit-text"] = theme.textColor;
|
|
259
288
|
}
|
|
@@ -270,24 +299,6 @@ function createThemeVariables(theme) {
|
|
|
270
299
|
}
|
|
271
300
|
return variables;
|
|
272
301
|
}
|
|
273
|
-
function getContrastingColor(color) {
|
|
274
|
-
const hex = color.trim();
|
|
275
|
-
if (!hex.startsWith("#")) {
|
|
276
|
-
return null;
|
|
277
|
-
}
|
|
278
|
-
const normalized = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
|
|
279
|
-
if (normalized.length !== 7) {
|
|
280
|
-
return null;
|
|
281
|
-
}
|
|
282
|
-
const r = parseInt(normalized.slice(1, 3), 16);
|
|
283
|
-
const g = parseInt(normalized.slice(3, 5), 16);
|
|
284
|
-
const b = parseInt(normalized.slice(5, 7), 16);
|
|
285
|
-
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
|
|
286
|
-
return null;
|
|
287
|
-
}
|
|
288
|
-
const brightness = (r * 299 + g * 587 + b * 114) / 1e3;
|
|
289
|
-
return brightness >= 140 ? "#0b1120" : "#ffffff";
|
|
290
|
-
}
|
|
291
302
|
function cn(...classes) {
|
|
292
303
|
return classes.filter(Boolean).join(" ");
|
|
293
304
|
}
|
|
@@ -357,6 +368,7 @@ export {
|
|
|
357
368
|
detectNetwork,
|
|
358
369
|
formatAmount,
|
|
359
370
|
formatPhone,
|
|
371
|
+
generateIdempotencyKey,
|
|
360
372
|
generateReference,
|
|
361
373
|
reevitReducer,
|
|
362
374
|
validatePhone
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api/client.ts","../src/utils.ts","../src/state.ts"],"sourcesContent":["/**\n * Reevit API Client\n * \n * Handles communication with the Reevit backend for payment operations.\n */\n\nimport type { PaymentMethod, ReevitCheckoutConfig, PaymentError, HubtelSessionResponse } from '../types';\n\n// API Response Types (matching backend handlers_payments.go)\nexport interface CreatePaymentIntentRequest {\n amount: number;\n currency: string;\n method?: string;\n country: string;\n customer_id?: string;\n metadata?: Record<string, unknown>;\n description?: string;\n policy?: {\n prefer?: string[];\n allowed_providers?: string[];\n max_amount?: number;\n blocked_bins?: string[];\n allowed_bins?: string[];\n velocity_max_per_minute?: number;\n };\n}\n\nexport interface PaymentIntentResponse {\n id: string;\n connection_id: string;\n provider: string;\n status: string;\n client_secret: string;\n psp_public_key: string;\n psp_credentials?: {\n merchantAccount?: string | number;\n basicAuth?: string;\n [key: string]: unknown;\n };\n amount: number;\n currency: string;\n fee_amount: number;\n fee_currency: string;\n net_amount: number;\n reference?: string;\n available_psps?: Array<{\n provider: string;\n name: string;\n methods: string[];\n countries?: string[];\n }>;\n branding?: Record<string, unknown>;\n}\n\nexport interface ConfirmPaymentRequest {\n provider_ref_id: string;\n provider_data?: Record<string, unknown>;\n}\n\nexport interface PaymentDetailResponse {\n id: string;\n connection_id: string;\n provider: string;\n method: string;\n status: string;\n amount: number;\n currency: string;\n fee_amount: number;\n fee_currency: string;\n net_amount: number;\n customer_id?: string;\n client_secret: string;\n provider_ref_id?: string;\n metadata?: Record<string, unknown>;\n created_at: string;\n updated_at: string;\n /** Payment source type (payment_link, api, subscription) */\n source?: 'payment_link' | 'api' | 'subscription';\n /** ID of the source (payment link ID, subscription ID, etc.) */\n source_id?: string;\n /** Human-readable description of the source (e.g., payment link name) */\n source_description?: string;\n}\n\nexport interface APIErrorResponse {\n code: string;\n message: string;\n details?: Record<string, string>;\n}\n\n// API Client configuration\nexport interface ReevitAPIClientConfig {\n /** Your Reevit public key */\n publicKey?: string;\n /** Base URL for the Reevit API (defaults to production) */\n baseUrl?: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n}\n\n// Default API base URLs\nconst API_BASE_URL_PRODUCTION = 'https://api.reevit.io';\nconst API_BASE_URL_SANDBOX = 'https://sandbox-api.reevit.io';\nconst DEFAULT_TIMEOUT = 30000; // 30 seconds\n\n/**\n * Determines if a public key is for sandbox mode\n */\nexport function isSandboxKey(publicKey: string): boolean {\n return publicKey.startsWith('pk_test_') ||\n publicKey.startsWith('pk_sandbox_') ||\n publicKey.startsWith('pfk_test_') ||\n publicKey.startsWith('pfk_sandbox_');\n}\n\n/**\n * Creates a PaymentError from an API error response\n */\nfunction createPaymentError(response: Response, errorData: APIErrorResponse): PaymentError {\n return {\n code: errorData.code || 'api_error',\n message: errorData.message || 'An unexpected error occurred',\n details: {\n httpStatus: response.status,\n ...errorData.details,\n },\n };\n}\n\n/**\n * Reevit API Client\n */\nexport class ReevitAPIClient {\n private readonly publicKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: ReevitAPIClientConfig) {\n this.publicKey = config.publicKey || '';\n this.baseUrl = config.baseUrl || (config.publicKey && isSandboxKey(config.publicKey)\n ? API_BASE_URL_SANDBOX\n : API_BASE_URL_PRODUCTION);\n this.timeout = config.timeout || DEFAULT_TIMEOUT;\n }\n\n /**\n * Makes an authenticated API request\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<{ data?: T; error?: PaymentError }> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n // Generate headers with idempotency key for mutating requests\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-Reevit-Client': '@reevit/core',\n 'X-Reevit-Client-Version': '0.3.2',\n };\n if (this.publicKey) {\n headers['X-Reevit-Key'] = this.publicKey;\n }\n\n if (method === 'POST' || method === 'PATCH' || method === 'PUT') {\n headers['Idempotency-Key'] = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;\n }\n\n try {\n const response = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const responseData = await response.json().catch(() => ({}));\n\n if (!response.ok) {\n return {\n error: createPaymentError(response, responseData as APIErrorResponse),\n };\n }\n\n return { data: responseData as T };\n } catch (err) {\n clearTimeout(timeoutId);\n\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return {\n error: {\n code: 'request_timeout',\n message: 'The request timed out. Please try again.',\n },\n };\n }\n\n if (err.message.includes('Failed to fetch') || err.message.includes('NetworkError')) {\n return {\n error: {\n code: 'network_error',\n message: 'Unable to connect to Reevit. Please check your internet connection.',\n },\n };\n }\n }\n\n return {\n error: {\n code: 'unknown_error',\n message: 'An unexpected error occurred. Please try again.',\n },\n };\n }\n }\n\n /**\n * Creates a payment intent\n */\n async createPaymentIntent(\n config: ReevitCheckoutConfig,\n method?: PaymentMethod,\n country: string = 'GH',\n options?: { preferredProviders?: string[]; allowedProviders?: string[] }\n ): Promise<{ data?: PaymentIntentResponse; error?: PaymentError }> {\n // Build metadata with customer_email for PSP providers that require it\n const metadata: Record<string, unknown> = { ...config.metadata };\n if (config.email) {\n metadata.customer_email = config.email;\n }\n if (config.phone) {\n metadata.customer_phone = config.phone;\n }\n\n const request: CreatePaymentIntentRequest = {\n amount: config.amount,\n currency: config.currency,\n country,\n customer_id: config.email || (config.metadata?.customerId as string | undefined),\n metadata,\n };\n\n if (method) {\n request.method = this.mapPaymentMethod(method);\n }\n\n if (options?.preferredProviders?.length || options?.allowedProviders?.length) {\n request.policy = {\n prefer: options?.preferredProviders,\n allowed_providers: options?.allowedProviders,\n };\n }\n\n return this.request<PaymentIntentResponse>('POST', '/v1/payments/intents', request);\n }\n\n /**\n * Retrieves a payment intent by ID\n */\n async getPaymentIntent(paymentId: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('GET', `/v1/payments/${paymentId}`);\n }\n\n /**\n * Confirms a payment after PSP callback\n */\n async confirmPayment(paymentId: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('POST', `/v1/payments/${paymentId}/confirm`);\n }\n\n /**\n * Confirms a payment intent using client secret (public endpoint)\n */\n async confirmPaymentIntent(paymentId: string, clientSecret: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('POST', `/v1/payments/${paymentId}/confirm-intent?client_secret=${clientSecret}`);\n }\n\n /**\n * Cancels a payment intent\n */\n async cancelPaymentIntent(paymentId: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('POST', `/v1/payments/${paymentId}/cancel`);\n }\n\n /**\n * Creates a Hubtel session token for secure checkout\n * Returns a short-lived token that contains Hubtel credentials\n * Credentials are never exposed to the client directly\n */\n async createHubtelSession(\n paymentId: string,\n clientSecret?: string\n ): Promise<{ data?: HubtelSessionResponse; error?: PaymentError }> {\n const query = clientSecret ? `?client_secret=${encodeURIComponent(clientSecret)}` : '';\n return this.request<HubtelSessionResponse>('POST', `/v1/payments/hubtel/sessions/${paymentId}${query}`);\n }\n\n /**\n * Maps SDK payment method to backend format\n */\n private mapPaymentMethod(method: PaymentMethod): string {\n switch (method) {\n case 'card':\n return 'card';\n case 'mobile_money':\n return 'mobile_money';\n case 'bank_transfer':\n return 'bank_transfer';\n default:\n return method;\n }\n }\n}\n\n/**\n * Creates a new Reevit API client instance\n */\nexport function createReevitClient(config: ReevitAPIClientConfig): ReevitAPIClient {\n return new ReevitAPIClient(config);\n}\n","/**\n * Utility Functions\n * Shared utilities for Reevit SDKs\n */\n\nimport type { MobileMoneyNetwork, ReevitTheme } from './types';\n\n/**\n * Formats an amount from smallest currency unit to display format\n */\nexport function formatAmount(amount: number, currency: string): string {\n const majorUnit = amount / 100;\n\n const currencyFormats: Record<string, { locale: string; minimumFractionDigits: number }> = {\n GHS: { locale: 'en-GH', minimumFractionDigits: 2 },\n NGN: { locale: 'en-NG', minimumFractionDigits: 2 },\n KES: { locale: 'en-KE', minimumFractionDigits: 2 },\n USD: { locale: 'en-US', minimumFractionDigits: 2 },\n EUR: { locale: 'de-DE', minimumFractionDigits: 2 },\n GBP: { locale: 'en-GB', minimumFractionDigits: 2 },\n };\n\n const format = currencyFormats[currency.toUpperCase()] || { locale: 'en-US', minimumFractionDigits: 2 };\n\n try {\n return new Intl.NumberFormat(format.locale, {\n style: 'currency',\n currency: currency.toUpperCase(),\n minimumFractionDigits: format.minimumFractionDigits,\n }).format(majorUnit);\n } catch {\n // Fallback for unsupported currencies\n return `${currency} ${majorUnit.toFixed(2)}`;\n }\n}\n\n/**\n * Generates a unique payment reference\n */\nexport function generateReference(prefix: string = 'reevit'): string {\n const timestamp = Date.now().toString(36);\n const random = Math.random().toString(36).substring(2, 8);\n return `${prefix}_${timestamp}_${random}`;\n}\n\n/**\n * Validates a phone number for mobile money\n */\nexport function validatePhone(phone: string, country: string = 'GH'): boolean {\n // Remove non-digit characters\n const digits = phone.replace(/\\D/g, '');\n\n const patterns: Record<string, RegExp> = {\n GH: /^(?:233|0)?[235][0-9]{8}$/, // Ghana\n NG: /^(?:234|0)?[789][01][0-9]{8}$/, // Nigeria\n KE: /^(?:254|0)?[17][0-9]{8}$/, // Kenya\n };\n\n const pattern = patterns[country.toUpperCase()];\n if (!pattern) return digits.length >= 10;\n\n return pattern.test(digits);\n}\n\n/**\n * Formats a phone number for display\n */\nexport function formatPhone(phone: string, country: string = 'GH'): string {\n const digits = phone.replace(/\\D/g, '');\n\n if (country === 'GH') {\n // Format as 0XX XXX XXXX\n if (digits.startsWith('233') && digits.length === 12) {\n const local = '0' + digits.slice(3);\n return `${local.slice(0, 3)} ${local.slice(3, 6)} ${local.slice(6)}`;\n }\n if (digits.length === 10 && digits.startsWith('0')) {\n return `${digits.slice(0, 3)} ${digits.slice(3, 6)} ${digits.slice(6)}`;\n }\n }\n\n return phone;\n}\n\n/**\n * Detects mobile money network from phone number (Ghana)\n */\nexport function detectNetwork(phone: string): MobileMoneyNetwork | null {\n const digits = phone.replace(/\\D/g, '');\n\n // Get the network prefix (first 3 digits after country code or 0)\n let prefix: string;\n if (digits.startsWith('233')) {\n prefix = digits.slice(3, 5);\n } else if (digits.startsWith('0')) {\n prefix = digits.slice(1, 3);\n } else {\n prefix = digits.slice(0, 2);\n }\n\n // Ghana network prefixes\n const mtnPrefixes = ['24', '25', '53', '54', '55', '59'];\n const telecelPrefixes = ['20', '50'];\n const airteltigoPrefixes = ['26', '27', '56', '57'];\n\n if (mtnPrefixes.includes(prefix)) return 'mtn';\n if (telecelPrefixes.includes(prefix)) return 'telecel';\n if (airteltigoPrefixes.includes(prefix)) return 'airteltigo';\n\n return null;\n}\n\n/**\n * Creates CSS custom property variables from theme\n */\nexport function createThemeVariables(theme: ReevitTheme): Record<string, string> {\n const variables: Record<string, string> = {};\n\n if (theme.primaryColor) {\n variables['--reevit-primary'] = theme.primaryColor;\n if (theme.primaryForegroundColor) {\n variables['--reevit-primary-foreground'] = theme.primaryForegroundColor;\n } else {\n const contrast = getContrastingColor(theme.primaryColor);\n if (contrast) {\n variables['--reevit-primary-foreground'] = contrast;\n }\n }\n }\n if (theme.backgroundColor) {\n variables['--reevit-background'] = theme.backgroundColor;\n }\n if (theme.surfaceColor) {\n variables['--reevit-surface'] = theme.surfaceColor;\n }\n if (theme.textColor) {\n variables['--reevit-text'] = theme.textColor;\n }\n if (theme.mutedTextColor) {\n variables['--reevit-text-secondary'] = theme.mutedTextColor;\n }\n if (theme.borderRadius) {\n variables['--reevit-radius'] = theme.borderRadius;\n variables['--reevit-radius-sm'] = theme.borderRadius;\n variables['--reevit-radius-lg'] = theme.borderRadius;\n }\n if (theme.fontFamily) {\n variables['--reevit-font'] = theme.fontFamily;\n }\n\n return variables;\n}\n\nfunction getContrastingColor(color: string): string | null {\n const hex = color.trim();\n if (!hex.startsWith('#')) {\n return null;\n }\n\n const normalized = hex.length === 4\n ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`\n : hex;\n\n if (normalized.length !== 7) {\n return null;\n }\n\n const r = parseInt(normalized.slice(1, 3), 16);\n const g = parseInt(normalized.slice(3, 5), 16);\n const b = parseInt(normalized.slice(5, 7), 16);\n\n if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {\n return null;\n }\n\n const brightness = (r * 299 + g * 587 + b * 114) / 1000;\n return brightness >= 140 ? '#0b1120' : '#ffffff';\n}\n\n/**\n * Simple class name concatenation utility\n */\nexport function cn(...classes: (string | boolean | undefined | null)[]): string {\n return classes.filter(Boolean).join(' ');\n}\n\n/**\n * Detects country code from currency\n */\nexport function detectCountryFromCurrency(currency: string): string {\n const currencyToCountry: Record<string, string> = {\n GHS: 'GH',\n NGN: 'NG',\n KES: 'KE',\n UGX: 'UG',\n TZS: 'TZ',\n ZAR: 'ZA',\n XOF: 'CI',\n XAF: 'CM',\n USD: 'US',\n EUR: 'DE',\n GBP: 'GB',\n };\n\n return currencyToCountry[currency.toUpperCase()] || 'GH';\n}\n","/**\n * Reevit State Machine\n * Shared state management logic for all SDKs\n */\n\nimport type { CheckoutState, PaymentIntent, PaymentMethod, PaymentResult, PaymentError } from './types';\n\n// State shape\nexport interface ReevitState {\n status: CheckoutState;\n paymentIntent: PaymentIntent | null;\n selectedMethod: PaymentMethod | null;\n error: PaymentError | null;\n result: PaymentResult | null;\n}\n\n// Actions\nexport type ReevitAction =\n | { type: 'INIT_START' }\n | { type: 'INIT_SUCCESS'; payload: PaymentIntent }\n | { type: 'INIT_ERROR'; payload: PaymentError }\n | { type: 'SELECT_METHOD'; payload: PaymentMethod }\n | { type: 'PROCESS_START' }\n | { type: 'PROCESS_SUCCESS'; payload: PaymentResult }\n | { type: 'PROCESS_ERROR'; payload: PaymentError }\n | { type: 'RESET' }\n | { type: 'CLOSE' };\n\n/**\n * Creates the initial state for the checkout\n */\nexport function createInitialState(): ReevitState {\n return {\n status: 'idle',\n paymentIntent: null,\n selectedMethod: null,\n error: null,\n result: null,\n };\n}\n\n/**\n * State reducer for checkout flow\n */\nexport function reevitReducer(state: ReevitState, action: ReevitAction): ReevitState {\n switch (action.type) {\n case 'INIT_START':\n return { ...state, status: 'loading', error: null };\n case 'INIT_SUCCESS':\n return {\n ...state,\n status: 'ready',\n paymentIntent: action.payload,\n selectedMethod:\n action.payload.availableMethods?.length === 1 ? action.payload.availableMethods[0] : null,\n };\n case 'INIT_ERROR':\n return { ...state, status: 'failed', error: action.payload };\n case 'SELECT_METHOD':\n return { ...state, status: 'method_selected', selectedMethod: action.payload };\n case 'PROCESS_START':\n return { ...state, status: 'processing', error: null };\n case 'PROCESS_SUCCESS':\n return { ...state, status: 'success', result: action.payload };\n case 'PROCESS_ERROR':\n return { ...state, status: 'failed', error: action.payload };\n case 'RESET':\n return { ...createInitialState(), status: 'ready', paymentIntent: state.paymentIntent };\n case 'CLOSE':\n return { ...state, status: 'closed' };\n default:\n return state;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/api/client.ts","../src/utils.ts","../src/state.ts"],"sourcesContent":["/**\n * Reevit API Client\n * \n * Handles communication with the Reevit backend for payment operations.\n */\n\nimport type { PaymentMethod, ReevitCheckoutConfig, PaymentError, HubtelSessionResponse } from '../types';\n\n// API Response Types (matching backend handlers_payments.go)\nexport interface CreatePaymentIntentRequest {\n amount: number;\n currency: string;\n method?: string;\n country: string;\n customer_id?: string;\n metadata?: Record<string, unknown>;\n description?: string;\n policy?: {\n prefer?: string[];\n allowed_providers?: string[];\n max_amount?: number;\n blocked_bins?: string[];\n allowed_bins?: string[];\n velocity_max_per_minute?: number;\n };\n}\n\nexport interface PaymentIntentResponse {\n id: string;\n org_id?: string;\n connection_id: string;\n provider: string;\n status: string;\n client_secret: string;\n psp_public_key: string;\n psp_credentials?: {\n merchantAccount?: string | number;\n basicAuth?: string;\n [key: string]: unknown;\n };\n amount: number;\n currency: string;\n fee_amount: number;\n fee_currency: string;\n net_amount: number;\n reference?: string;\n available_psps?: Array<{\n provider: string;\n name: string;\n methods: string[];\n countries?: string[];\n }>;\n branding?: Record<string, unknown>;\n}\n\nexport interface ConfirmPaymentRequest {\n provider_ref_id: string;\n provider_data?: Record<string, unknown>;\n}\n\nexport interface PaymentDetailResponse {\n id: string;\n connection_id: string;\n provider: string;\n method: string;\n status: string;\n amount: number;\n currency: string;\n fee_amount: number;\n fee_currency: string;\n net_amount: number;\n customer_id?: string;\n client_secret: string;\n provider_ref_id?: string;\n metadata?: Record<string, unknown>;\n created_at: string;\n updated_at: string;\n /** Payment source type (payment_link, api, subscription) */\n source?: 'payment_link' | 'api' | 'subscription';\n /** ID of the source (payment link ID, subscription ID, etc.) */\n source_id?: string;\n /** Human-readable description of the source (e.g., payment link name) */\n source_description?: string;\n}\n\nexport interface APIErrorResponse {\n code: string;\n message: string;\n details?: Record<string, string>;\n}\n\n// API Client configuration\nexport interface ReevitAPIClientConfig {\n /** Your Reevit public key */\n publicKey?: string;\n /** Base URL for the Reevit API (defaults to production) */\n baseUrl?: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n}\n\n// Default API base URLs\nconst API_BASE_URL_PRODUCTION = 'https://api.reevit.io';\nconst API_BASE_URL_SANDBOX = 'https://sandbox-api.reevit.io';\nconst DEFAULT_TIMEOUT = 30000; // 30 seconds\n\n/**\n * Determines if a public key is for sandbox mode\n */\nexport function isSandboxKey(publicKey: string): boolean {\n return publicKey.startsWith('pk_test_') ||\n publicKey.startsWith('pk_sandbox_') ||\n publicKey.startsWith('pfk_test_') ||\n publicKey.startsWith('pfk_sandbox_');\n}\n\n/**\n * Creates a PaymentError from an API error response\n */\nfunction createPaymentError(response: Response, errorData: APIErrorResponse): PaymentError {\n return {\n code: errorData.code || 'api_error',\n message: errorData.message || 'An unexpected error occurred',\n details: {\n httpStatus: response.status,\n ...errorData.details,\n },\n };\n}\n\n/**\n * Generates a deterministic idempotency key based on input parameters\n * Uses a simple hash function suitable for browser environments\n * Exported for use by SDK hooks (e.g., payment link flows)\n */\nexport function generateIdempotencyKey(params: Record<string, unknown>): string {\n // Create a stable string representation of the parameters\n const sortedKeys = Object.keys(params).sort();\n const stableString = sortedKeys\n .map(key => `${key}:${JSON.stringify(params[key])}`)\n .join('|');\n\n // Simple hash function (djb2 algorithm)\n let hash = 5381;\n for (let i = 0; i < stableString.length; i++) {\n hash = ((hash << 5) + hash) + stableString.charCodeAt(i);\n hash = hash & hash; // Convert to 32-bit integer\n }\n\n // Convert to positive hex string\n const hashHex = (hash >>> 0).toString(16);\n\n // Add a time bucket (5-minute windows) to allow retries within a reasonable window\n // but prevent keys from being reused across completely different sessions\n const timeBucket = Math.floor(Date.now() / (5 * 60 * 1000));\n\n return `reevit_${timeBucket}_${hashHex}`;\n}\n\n/**\n * Reevit API Client\n */\nexport class ReevitAPIClient {\n private readonly publicKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: ReevitAPIClientConfig) {\n this.publicKey = config.publicKey || '';\n this.baseUrl = config.baseUrl || (config.publicKey && isSandboxKey(config.publicKey)\n ? API_BASE_URL_SANDBOX\n : API_BASE_URL_PRODUCTION);\n this.timeout = config.timeout || DEFAULT_TIMEOUT;\n }\n\n /**\n * Makes an authenticated API request\n * @param idempotencyKey Optional deterministic idempotency key for the request\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n idempotencyKey?: string\n ): Promise<{ data?: T; error?: PaymentError }> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n // Generate headers with idempotency key for mutating requests\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-Reevit-Client': '@reevit/core',\n 'X-Reevit-Client-Version': '0.5.9',\n };\n if (this.publicKey) {\n headers['X-Reevit-Key'] = this.publicKey;\n }\n\n if (method === 'POST' || method === 'PATCH' || method === 'PUT') {\n // Use provided deterministic key, or generate one based on request body\n headers['Idempotency-Key'] = idempotencyKey ||\n (body ? generateIdempotencyKey(body as Record<string, unknown>) : `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`);\n }\n\n try {\n const response = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const responseData = await response.json().catch(() => ({}));\n\n if (!response.ok) {\n return {\n error: createPaymentError(response, responseData as APIErrorResponse),\n };\n }\n\n return { data: responseData as T };\n } catch (err) {\n clearTimeout(timeoutId);\n\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return {\n error: {\n code: 'request_timeout',\n message: 'The request timed out. Please try again.',\n },\n };\n }\n\n if (err.message.includes('Failed to fetch') || err.message.includes('NetworkError')) {\n return {\n error: {\n code: 'network_error',\n message: 'Unable to connect to Reevit. Please check your internet connection.',\n },\n };\n }\n }\n\n return {\n error: {\n code: 'unknown_error',\n message: 'An unexpected error occurred. Please try again.',\n },\n };\n }\n }\n\n /**\n * Creates a payment intent\n */\n async createPaymentIntent(\n config: ReevitCheckoutConfig,\n method?: PaymentMethod,\n country: string = 'GH',\n options?: { preferredProviders?: string[]; allowedProviders?: string[] }\n ): Promise<{ data?: PaymentIntentResponse; error?: PaymentError }> {\n // Build metadata with customer_email for PSP providers that require it\n const metadata: Record<string, unknown> = { ...config.metadata };\n if (config.email) {\n metadata.customer_email = config.email;\n }\n if (config.phone) {\n metadata.customer_phone = config.phone;\n }\n\n const request: CreatePaymentIntentRequest = {\n amount: config.amount,\n currency: config.currency,\n country,\n customer_id: config.email || (config.metadata?.customerId as string | undefined),\n metadata,\n };\n\n if (method) {\n request.method = this.mapPaymentMethod(method);\n }\n\n if (options?.preferredProviders?.length || options?.allowedProviders?.length) {\n request.policy = {\n prefer: options?.preferredProviders,\n allowed_providers: options?.allowedProviders,\n };\n }\n\n // Generate a deterministic idempotency key based on payment parameters\n // This ensures that duplicate requests for the same payment return the same intent\n const idempotencyKey = generateIdempotencyKey({\n amount: config.amount,\n currency: config.currency,\n customer: config.email || config.metadata?.customerId || '',\n reference: config.reference || '',\n method: method || '',\n provider: options?.preferredProviders?.[0] || options?.allowedProviders?.[0] || '',\n publicKey: this.publicKey,\n });\n\n return this.request<PaymentIntentResponse>('POST', '/v1/payments/intents', request, idempotencyKey);\n }\n\n /**\n * Retrieves a payment intent by ID\n */\n async getPaymentIntent(paymentId: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('GET', `/v1/payments/${paymentId}`);\n }\n\n /**\n * Confirms a payment after PSP callback\n */\n async confirmPayment(paymentId: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('POST', `/v1/payments/${paymentId}/confirm`);\n }\n\n /**\n * Confirms a payment intent using client secret (public endpoint)\n */\n async confirmPaymentIntent(paymentId: string, clientSecret: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('POST', `/v1/payments/${paymentId}/confirm-intent?client_secret=${clientSecret}`);\n }\n\n /**\n * Cancels a payment intent\n */\n async cancelPaymentIntent(paymentId: string): Promise<{ data?: PaymentDetailResponse; error?: PaymentError }> {\n return this.request<PaymentDetailResponse>('POST', `/v1/payments/${paymentId}/cancel`);\n }\n\n /**\n * Creates a Hubtel session token for secure checkout\n * Returns a short-lived token that contains Hubtel credentials\n * Credentials are never exposed to the client directly\n */\n async createHubtelSession(\n paymentId: string,\n clientSecret?: string\n ): Promise<{ data?: HubtelSessionResponse; error?: PaymentError }> {\n const query = clientSecret ? `?client_secret=${encodeURIComponent(clientSecret)}` : '';\n return this.request<HubtelSessionResponse>('POST', `/v1/payments/hubtel/sessions/${paymentId}${query}`);\n }\n\n /**\n * Maps SDK payment method to backend format\n */\n private mapPaymentMethod(method: PaymentMethod): string {\n switch (method) {\n case 'card':\n return 'card';\n case 'mobile_money':\n return 'mobile_money';\n case 'bank_transfer':\n return 'bank_transfer';\n default:\n return method;\n }\n }\n}\n\n/**\n * Creates a new Reevit API client instance\n */\nexport function createReevitClient(config: ReevitAPIClientConfig): ReevitAPIClient {\n return new ReevitAPIClient(config);\n}\n","/**\n * Utility Functions\n * Shared utilities for Reevit SDKs\n */\n\nimport type { MobileMoneyNetwork, ReevitTheme } from './types';\n\n/**\n * Formats an amount from smallest currency unit to display format\n */\nexport function formatAmount(amount: number, currency: string): string {\n const majorUnit = amount / 100;\n\n const currencyFormats: Record<string, { locale: string; minimumFractionDigits: number }> = {\n GHS: { locale: 'en-GH', minimumFractionDigits: 2 },\n NGN: { locale: 'en-NG', minimumFractionDigits: 2 },\n KES: { locale: 'en-KE', minimumFractionDigits: 2 },\n USD: { locale: 'en-US', minimumFractionDigits: 2 },\n EUR: { locale: 'de-DE', minimumFractionDigits: 2 },\n GBP: { locale: 'en-GB', minimumFractionDigits: 2 },\n };\n\n const format = currencyFormats[currency.toUpperCase()] || { locale: 'en-US', minimumFractionDigits: 2 };\n\n try {\n return new Intl.NumberFormat(format.locale, {\n style: 'currency',\n currency: currency.toUpperCase(),\n minimumFractionDigits: format.minimumFractionDigits,\n }).format(majorUnit);\n } catch {\n // Fallback for unsupported currencies\n return `${currency} ${majorUnit.toFixed(2)}`;\n }\n}\n\n/**\n * Generates a unique payment reference\n */\nexport function generateReference(prefix: string = 'reevit'): string {\n const timestamp = Date.now().toString(36);\n const random = Math.random().toString(36).substring(2, 8);\n return `${prefix}_${timestamp}_${random}`;\n}\n\n/**\n * Validates a phone number for mobile money\n */\nexport function validatePhone(phone: string, country: string = 'GH'): boolean {\n // Remove non-digit characters\n const digits = phone.replace(/\\D/g, '');\n\n const patterns: Record<string, RegExp> = {\n GH: /^(?:233|0)?[235][0-9]{8}$/, // Ghana\n NG: /^(?:234|0)?[789][01][0-9]{8}$/, // Nigeria\n KE: /^(?:254|0)?[17][0-9]{8}$/, // Kenya\n };\n\n const pattern = patterns[country.toUpperCase()];\n if (!pattern) return digits.length >= 10;\n\n return pattern.test(digits);\n}\n\n/**\n * Formats a phone number for display\n */\nexport function formatPhone(phone: string, country: string = 'GH'): string {\n const digits = phone.replace(/\\D/g, '');\n\n if (country === 'GH') {\n // Format as 0XX XXX XXXX\n if (digits.startsWith('233') && digits.length === 12) {\n const local = '0' + digits.slice(3);\n return `${local.slice(0, 3)} ${local.slice(3, 6)} ${local.slice(6)}`;\n }\n if (digits.length === 10 && digits.startsWith('0')) {\n return `${digits.slice(0, 3)} ${digits.slice(3, 6)} ${digits.slice(6)}`;\n }\n }\n\n return phone;\n}\n\n/**\n * Detects mobile money network from phone number (Ghana)\n */\nexport function detectNetwork(phone: string): MobileMoneyNetwork | null {\n const digits = phone.replace(/\\D/g, '');\n\n // Get the network prefix (first 3 digits after country code or 0)\n let prefix: string;\n if (digits.startsWith('233')) {\n prefix = digits.slice(3, 5);\n } else if (digits.startsWith('0')) {\n prefix = digits.slice(1, 3);\n } else {\n prefix = digits.slice(0, 2);\n }\n\n // Ghana network prefixes\n const mtnPrefixes = ['24', '25', '53', '54', '55', '59'];\n const telecelPrefixes = ['20', '50'];\n const airteltigoPrefixes = ['26', '27', '56', '57'];\n\n if (mtnPrefixes.includes(prefix)) return 'mtn';\n if (telecelPrefixes.includes(prefix)) return 'telecel';\n if (airteltigoPrefixes.includes(prefix)) return 'airteltigo';\n\n return null;\n}\n\n/**\n * Creates CSS custom property variables from theme\n */\nexport function createThemeVariables(theme: ReevitTheme): Record<string, string> {\n const variables: Record<string, string> = {};\n\n // Primary color = main text color\n if (theme.primaryColor) {\n variables['--reevit-text'] = theme.primaryColor;\n }\n\n // Primary foreground = description/secondary text color\n if (theme.primaryForegroundColor) {\n variables['--reevit-text-secondary'] = theme.primaryForegroundColor;\n variables['--reevit-muted'] = theme.primaryForegroundColor;\n }\n\n // Button colors\n if (theme.buttonBackgroundColor) {\n variables['--reevit-primary'] = theme.buttonBackgroundColor;\n variables['--reevit-primary-hover'] = theme.buttonBackgroundColor;\n }\n if (theme.buttonTextColor) {\n variables['--reevit-primary-foreground'] = theme.buttonTextColor;\n }\n\n // Background and surface colors\n if (theme.backgroundColor) {\n variables['--reevit-background'] = theme.backgroundColor;\n variables['--reevit-surface'] = theme.backgroundColor;\n }\n if (theme.surfaceColor) {\n variables['--reevit-surface'] = theme.surfaceColor;\n }\n\n // Border color\n if (theme.borderColor) {\n variables['--reevit-border'] = theme.borderColor;\n }\n\n // Legacy text color support\n if (theme.textColor) {\n variables['--reevit-text'] = theme.textColor;\n }\n if (theme.mutedTextColor) {\n variables['--reevit-text-secondary'] = theme.mutedTextColor;\n }\n\n // Border radius\n if (theme.borderRadius) {\n variables['--reevit-radius'] = theme.borderRadius;\n variables['--reevit-radius-sm'] = theme.borderRadius;\n variables['--reevit-radius-lg'] = theme.borderRadius;\n }\n\n // Font family\n if (theme.fontFamily) {\n variables['--reevit-font'] = theme.fontFamily;\n }\n\n return variables;\n}\n\nfunction getContrastingColor(color: string): string | null {\n const hex = color.trim();\n if (!hex.startsWith('#')) {\n return null;\n }\n\n const normalized = hex.length === 4\n ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`\n : hex;\n\n if (normalized.length !== 7) {\n return null;\n }\n\n const r = parseInt(normalized.slice(1, 3), 16);\n const g = parseInt(normalized.slice(3, 5), 16);\n const b = parseInt(normalized.slice(5, 7), 16);\n\n if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {\n return null;\n }\n\n const brightness = (r * 299 + g * 587 + b * 114) / 1000;\n return brightness >= 140 ? '#0b1120' : '#ffffff';\n}\n\n/**\n * Simple class name concatenation utility\n */\nexport function cn(...classes: (string | boolean | undefined | null)[]): string {\n return classes.filter(Boolean).join(' ');\n}\n\n/**\n * Detects country code from currency\n */\nexport function detectCountryFromCurrency(currency: string): string {\n const currencyToCountry: Record<string, string> = {\n GHS: 'GH',\n NGN: 'NG',\n KES: 'KE',\n UGX: 'UG',\n TZS: 'TZ',\n ZAR: 'ZA',\n XOF: 'CI',\n XAF: 'CM',\n USD: 'US',\n EUR: 'DE',\n GBP: 'GB',\n };\n\n return currencyToCountry[currency.toUpperCase()] || 'GH';\n}\n","/**\n * Reevit State Machine\n * Shared state management logic for all SDKs\n */\n\nimport type { CheckoutState, PaymentIntent, PaymentMethod, PaymentResult, PaymentError } from './types';\n\n// State shape\nexport interface ReevitState {\n status: CheckoutState;\n paymentIntent: PaymentIntent | null;\n selectedMethod: PaymentMethod | null;\n error: PaymentError | null;\n result: PaymentResult | null;\n}\n\n// Actions\nexport type ReevitAction =\n | { type: 'INIT_START' }\n | { type: 'INIT_SUCCESS'; payload: PaymentIntent }\n | { type: 'INIT_ERROR'; payload: PaymentError }\n | { type: 'SELECT_METHOD'; payload: PaymentMethod }\n | { type: 'PROCESS_START' }\n | { type: 'PROCESS_SUCCESS'; payload: PaymentResult }\n | { type: 'PROCESS_ERROR'; payload: PaymentError }\n | { type: 'RESET' }\n | { type: 'CLOSE' };\n\n/**\n * Creates the initial state for the checkout\n */\nexport function createInitialState(): ReevitState {\n return {\n status: 'idle',\n paymentIntent: null,\n selectedMethod: null,\n error: null,\n result: null,\n };\n}\n\n/**\n * State reducer for checkout flow\n */\nexport function reevitReducer(state: ReevitState, action: ReevitAction): ReevitState {\n switch (action.type) {\n case 'INIT_START':\n return { ...state, status: 'loading', error: null };\n case 'INIT_SUCCESS':\n return {\n ...state,\n status: 'ready',\n paymentIntent: action.payload,\n selectedMethod:\n action.payload.availableMethods?.length === 1 ? action.payload.availableMethods[0] : null,\n };\n case 'INIT_ERROR':\n return { ...state, status: 'failed', error: action.payload };\n case 'SELECT_METHOD':\n return { ...state, status: 'method_selected', selectedMethod: action.payload };\n case 'PROCESS_START':\n return { ...state, status: 'processing', error: null };\n case 'PROCESS_SUCCESS':\n return { ...state, status: 'success', result: action.payload };\n case 'PROCESS_ERROR':\n return { ...state, status: 'failed', error: action.payload };\n case 'RESET':\n return { ...createInitialState(), status: 'ready', paymentIntent: state.paymentIntent };\n case 'CLOSE':\n return { ...state, status: 'closed' };\n default:\n return state;\n }\n}\n"],"mappings":"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","names":[]}
|