@reevit/core 0.8.1 → 0.9.1
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/README.md +94 -3
- package/dist/index.d.mts +104 -13
- package/dist/index.d.ts +104 -13
- package/dist/index.js +241 -40
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +234 -40
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/api/client.ts","../src/utils.ts","../src/intent.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// Intent identity + cache helpers\nexport {\n resolveIntentIdentity,\n getIntentCacheEntry,\n cacheIntentPromise,\n cacheIntentResponse,\n clearIntentCacheEntry,\n type IntentCacheEntry,\n} from './intent';\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 provider_ref_id?: 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 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('pfk_test_');\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 || 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.8.1',\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 = config.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>(\n 'POST',\n `/v1/payments/${paymentId}/confirm-intent?client_secret=${encodeURIComponent(clientSecret)}`\n );\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 * Intent identity + cache helpers\n */\n\nimport type { PaymentIntentResponse } from './api/client';\nimport { generateIdempotencyKey } from './api/client';\nimport type { PaymentMethod, ReevitCheckoutConfig } from './types';\nimport { generateReference } from './utils';\n\nconst INTENT_CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\nexport interface IntentIdentityOptions {\n config: ReevitCheckoutConfig;\n method?: PaymentMethod;\n preferredProvider?: string;\n allowedProviders?: string[];\n publicKey?: string;\n}\n\nexport interface IntentCacheEntry {\n promise?: Promise<PaymentIntentResponse>;\n response?: PaymentIntentResponse;\n expiresAt: number;\n reference?: string;\n}\n\nconst intentCache = new Map<string, IntentCacheEntry>();\n\nfunction pruneIntentCache(now: number = Date.now()): void {\n for (const [key, entry] of intentCache) {\n if (entry.expiresAt <= now) {\n intentCache.delete(key);\n }\n }\n}\n\nfunction getIntentCacheEntryInternal(key: string): IntentCacheEntry | undefined {\n const entry = intentCache.get(key);\n if (!entry) {\n return undefined;\n }\n if (entry.expiresAt <= Date.now()) {\n intentCache.delete(key);\n return undefined;\n }\n return entry;\n}\n\nfunction setIntentCacheEntryInternal(key: string, update: Partial<IntentCacheEntry>): IntentCacheEntry {\n const now = Date.now();\n const existing = getIntentCacheEntryInternal(key);\n const next: IntentCacheEntry = {\n ...existing,\n ...update,\n expiresAt: now + INTENT_CACHE_TTL_MS,\n };\n intentCache.set(key, next);\n return next;\n}\n\nfunction buildIdempotencyPayload(options: IntentIdentityOptions): Record<string, unknown> {\n const { config, method, preferredProvider, allowedProviders, publicKey } = options;\n const payload: Record<string, unknown> = {\n amount: config.amount,\n currency: config.currency,\n email: config.email || '',\n phone: config.phone || '',\n customerName: config.customerName || '',\n paymentLinkCode: config.paymentLinkCode || '',\n paymentMethods: config.paymentMethods || [],\n metadata: config.metadata || {},\n customFields: config.customFields || {},\n method: method || '',\n preferredProvider: preferredProvider || '',\n allowedProviders: allowedProviders || [],\n publicKey: publicKey || config.publicKey || '',\n };\n\n if (config.reference) {\n payload.reference = config.reference;\n }\n\n return payload;\n}\n\nexport function resolveIntentIdentity(options: IntentIdentityOptions): {\n idempotencyKey: string;\n reference: string;\n cacheEntry?: IntentCacheEntry;\n} {\n pruneIntentCache();\n\n const idempotencyKey =\n options.config.idempotencyKey || generateIdempotencyKey(buildIdempotencyPayload(options));\n const existing = getIntentCacheEntryInternal(idempotencyKey);\n const reference = options.config.reference || existing?.reference || generateReference();\n\n const cacheEntry = setIntentCacheEntryInternal(idempotencyKey, { reference });\n\n return { idempotencyKey, reference, cacheEntry };\n}\n\nexport function getIntentCacheEntry(idempotencyKey: string): IntentCacheEntry | undefined {\n pruneIntentCache();\n return getIntentCacheEntryInternal(idempotencyKey);\n}\n\nexport function cacheIntentPromise(\n idempotencyKey: string,\n promise: Promise<PaymentIntentResponse>\n): IntentCacheEntry {\n return setIntentCacheEntryInternal(idempotencyKey, { promise });\n}\n\nexport function cacheIntentResponse(\n idempotencyKey: string,\n response: PaymentIntentResponse\n): IntentCacheEntry {\n return setIntentCacheEntryInternal(idempotencyKey, { response, promise: undefined });\n}\n\nexport function clearIntentCacheEntry(idempotencyKey: string): void {\n intentCache.delete(idempotencyKey);\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/intent.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 createPaymentError,\n generateIdempotencyKey,\n newIdempotencyKey,\n attemptIdempotencyKey,\n clearIdempotencyAttemptKeys,\n isPaymentError,\n type ReevitAPIClientConfig,\n type CreatePaymentIntentRequest,\n type PaymentIntentResponse,\n type CheckoutSessionResponse,\n type PaymentDetailResponse,\n type ConfirmPaymentRequest,\n type APIErrorResponse,\n type ReevitAPIResult,\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 currencyExponent,\n toMinorUnits,\n generateReference,\n validatePhone,\n formatPhone,\n detectNetwork,\n detectCountryFromCurrency,\n createThemeVariables,\n cn,\n} from './utils';\n\n// Intent identity + cache helpers\nexport {\n resolveIntentIdentity,\n getIntentCacheEntry,\n cacheIntentPromise,\n cacheIntentResponse,\n clearIntentCacheEntry,\n type IntentCacheEntry,\n} from './intent';\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 provider_ref_id?: string;\n status: string;\n client_secret: string;\n session_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 CheckoutSessionResponse {\n id: string;\n client_secret: string;\n session_secret: string;\n payment_intent: PaymentIntentResponse;\n expires_at?: string;\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, unknown>;\n}\n\nexport type ReevitAPIResult<T> = { data: T; error?: never } | { data?: never; error: PaymentError };\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 DEFAULT_TIMEOUT = 30000; // 30 seconds\nlet hasWarnedAboutLiveBrowserIntents = false;\n\n/**\n * Determines if a public key is for sandbox mode\n */\nexport function isSandboxKey(publicKey: string): boolean {\n return publicKey.startsWith('pfk_test_');\n}\n\n/**\n * Creates a PaymentError from an API error response\n */\nexport function createPaymentError(response: Response, errorData: APIErrorResponse): PaymentError {\n return {\n code: errorData.code || 'api_error',\n message: errorData.message || 'An unexpected error occurred',\n recoverable: isRecoverableStatus(response.status),\n details: {\n httpStatus: response.status,\n requestId: response.headers.get('x-request-id') || response.headers.get('x-reevit-request-id') || undefined,\n ...errorData.details,\n },\n };\n}\n\nexport function isPaymentError(error: unknown): error is PaymentError {\n return typeof error === 'object' && error !== null && 'code' in error && 'message' in error;\n}\n\nexport function isRecoverableStatus(status: number): boolean {\n return status === 408 || status === 409 || status === 425 || status === 429 || status >= 500;\n}\n\n/**\n * Generates a deterministic **cache/lookup** key from input parameters.\n *\n * NEVER SEND THIS ON THE WIRE. It is a 32-bit djb2 hash bucketed into\n * 5-minute windows, so two unrelated shoppers can collide and be handed each\n * other's payment intent (and therefore each other's `client_secret`), and a\n * shopper legitimately buying the same item twice inside one window would be\n * charged once. Its only job is to identify \"the same checkout attempt\" inside\n * a single browser tab so the in-flight intent cache can dedupe a repeated\n * \"Continue\" click.\n *\n * The value actually sent as `Idempotency-Key` is produced by\n * {@link newIdempotencyKey} / {@link attemptIdempotencyKey}.\n *\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\nconst IDEMPOTENCY_STORE_PREFIX = 'reevit:idem:';\n\n/** Fallback store for SSR / privacy mode, where `sessionStorage` is unusable. */\nconst memoryAttemptKeys = new Map<string, string>();\n\nfunction getSessionStore(): Storage | null {\n try {\n const storage = (globalThis as { sessionStorage?: Storage }).sessionStorage;\n if (!storage) {\n return null;\n }\n // Safari private mode and some embedded webviews throw on write.\n const probe = `${IDEMPOTENCY_STORE_PREFIX}probe`;\n storage.setItem(probe, '1');\n storage.removeItem(probe);\n return storage;\n } catch {\n return null;\n }\n}\n\n/**\n * Generates a fresh, globally unique `Idempotency-Key` (RFC 4122 v4 UUID).\n * This is the only value that should ever be sent on the wire.\n */\nexport function newIdempotencyKey(): string {\n const cryptoObj = (globalThis as { crypto?: Crypto }).crypto;\n\n if (cryptoObj && typeof cryptoObj.randomUUID === 'function') {\n try {\n return cryptoObj.randomUUID();\n } catch {\n // fall through to the manual generator\n }\n }\n\n const bytes = new Uint8Array(16);\n if (cryptoObj && typeof cryptoObj.getRandomValues === 'function') {\n cryptoObj.getRandomValues(bytes);\n } else {\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n\n bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4\n bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 10\n\n const hex: string[] = [];\n for (let i = 0; i < bytes.length; i++) {\n hex.push(bytes[i].toString(16).padStart(2, '0'));\n }\n\n return [\n hex.slice(0, 4).join(''),\n hex.slice(4, 6).join(''),\n hex.slice(6, 8).join(''),\n hex.slice(8, 10).join(''),\n hex.slice(10, 16).join(''),\n ].join('-');\n}\n\n/**\n * Resolves the stable per-checkout-attempt wire key for a deterministic\n * lookup key (see {@link generateIdempotencyKey}).\n *\n * The first call for a lookup key mints a UUID and stores it in\n * `sessionStorage` (falling back to a module-level map when storage is\n * unavailable); every later call in the same tab returns that same UUID, so a\n * repeated \"Continue\" click is still deduped by the backend. A different tab,\n * a different shopper or a cleared store yields a different UUID.\n */\nexport function attemptIdempotencyKey(lookupKey: string): string {\n const storageKey = `${IDEMPOTENCY_STORE_PREFIX}${lookupKey}`;\n const store = getSessionStore();\n\n if (store) {\n try {\n const existing = store.getItem(storageKey);\n if (existing) {\n return existing;\n }\n const created = newIdempotencyKey();\n store.setItem(storageKey, created);\n return created;\n } catch {\n // fall through to the in-memory store\n }\n }\n\n const existing = memoryAttemptKeys.get(storageKey);\n if (existing) {\n return existing;\n }\n const created = newIdempotencyKey();\n memoryAttemptKeys.set(storageKey, created);\n return created;\n}\n\n/**\n * Forgets every stored per-attempt key, so the next checkout attempt gets a\n * fresh `Idempotency-Key`. Call it after a completed checkout (and in tests).\n */\nexport function clearIdempotencyAttemptKeys(): void {\n memoryAttemptKeys.clear();\n\n const store = getSessionStore();\n if (!store) {\n return;\n }\n\n try {\n const keys: string[] = [];\n for (let i = 0; i < store.length; i++) {\n const key = store.key(i);\n if (key && key.startsWith(IDEMPOTENCY_STORE_PREFIX)) {\n keys.push(key);\n }\n }\n for (const key of keys) {\n store.removeItem(key);\n }\n } catch {\n // nothing else we can do\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 || 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<ReevitAPIResult<T>> {\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.9.1',\n };\n if (this.publicKey) {\n headers['X-Reevit-Key'] = this.publicKey;\n }\n\n if (method === 'POST' || method === 'PATCH' || method === 'PUT') {\n // Never derive the wire key from the request body: a body hash collides\n // across unrelated shoppers. Fall back to a fresh UUID instead.\n headers['Idempotency-Key'] = idempotencyKey || newIdempotencyKey();\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 recoverable: true,\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 recoverable: true,\n },\n };\n }\n }\n\n return {\n error: {\n code: 'unknown_error',\n message: 'An unexpected error occurred. Please try again.',\n recoverable: true,\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 if (\n this.publicKey.startsWith('pfk_live_') &&\n !hasWarnedAboutLiveBrowserIntents &&\n typeof console !== 'undefined'\n ) {\n hasWarnedAboutLiveBrowserIntents = true;\n console.warn(\n 'Creating live payment intents from the browser is deprecated. Create a checkout session on your server and pass sessionSecret to the browser SDK instead.'\n );\n }\n\n if (typeof config.amount !== 'number' || !config.currency) {\n return {\n error: {\n code: 'invalid_checkout_config',\n message: 'amount and currency are required when creating a payment intent in the browser.',\n recoverable: false,\n },\n };\n }\n\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 // The deterministic hash identifies this checkout attempt *locally*; the\n // key we put on the wire is a UUID minted once per attempt and reused for\n // the life of the tab, so a repeated \"Continue\" click still dedupes while\n // two unrelated shoppers can never share a key.\n const idempotencyKey = config.idempotencyKey || attemptIdempotencyKey(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 * Retrieves a server-created checkout session using its public session secret.\n */\n async getCheckoutSession(sessionSecret: string): Promise<{ data?: CheckoutSessionResponse; error?: PaymentError }> {\n return this.request<CheckoutSessionResponse>(\n 'GET',\n `/v1/checkout/sessions/${encodeURIComponent(sessionSecret)}`\n );\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>(\n 'POST',\n `/v1/payments/${paymentId}/confirm-intent?client_secret=${encodeURIComponent(clientSecret)}`\n );\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\nconst CURRENCY_LOCALES: Record<string, string> = {\n GHS: 'en-GH',\n NGN: 'en-NG',\n KES: 'en-KE',\n USD: 'en-US',\n EUR: 'de-DE',\n GBP: 'en-GB',\n};\n\n/**\n * Currencies with no minor unit — the API's integer amount *is* the amount.\n * Used only when `Intl` cannot tell us (old or trimmed ICU builds).\n * Keep in sync with the reevit CLI's copy of this table.\n */\nconst ZERO_DECIMAL_CURRENCIES = new Set([\n 'XOF', 'XAF', 'RWF', 'UGX', 'JPY', 'KRW', 'BIF', 'GNF',\n 'VND', 'CLP', 'ISK', 'KMF', 'DJF', 'PYG', 'MGA',\n]);\n\n/**\n * Returns how many decimal places a currency's minor unit uses: 2 for GHS and\n * NGN, 0 for XOF, XAF, RWF, UGX, JPY and friends.\n *\n * Dividing every amount by 100 renders a 5,000 XOF charge as \"XOF 50.00\" while\n * the shopper is actually charged 5,000 — which is why this exists.\n */\nexport function currencyExponent(currency: string): number {\n const code = (currency || '').toUpperCase();\n\n try {\n const digits = new Intl.NumberFormat('en', {\n style: 'currency',\n currency: code,\n }).resolvedOptions().maximumFractionDigits;\n\n if (typeof digits === 'number' && Number.isFinite(digits)) {\n return digits;\n }\n } catch {\n // Unsupported currency code, or an ICU build without currency data.\n }\n\n return ZERO_DECIMAL_CURRENCIES.has(code) ? 0 : 2;\n}\n\n/**\n * Converts a major-unit amount (what a shopper types) into the minor units the\n * API expects: `toMinorUnits(45, 'GHS') === 4500`, `toMinorUnits(5000, 'XOF') === 5000`.\n */\nexport function toMinorUnits(major: number, currency: string): number {\n return Math.round(major * 10 ** currencyExponent(currency));\n}\n\n/**\n * Formats an amount from smallest currency unit to display format\n */\nexport function formatAmount(amount: number, currency: string): string {\n const code = (currency || '').toUpperCase();\n const exponent = currencyExponent(code);\n const majorUnit = amount / 10 ** exponent;\n const locale = CURRENCY_LOCALES[code] || 'en-US';\n\n try {\n return new Intl.NumberFormat(locale, {\n style: 'currency',\n currency: code,\n minimumFractionDigits: exponent,\n maximumFractionDigits: exponent,\n }).format(majorUnit);\n } catch {\n // Fallback for unsupported currencies\n return `${code} ${majorUnit.toFixed(exponent)}`;\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 * Intent identity + cache helpers\n *\n * Two different keys are in play here and mixing them up is a money bug:\n *\n * - the **lookup key** is the deterministic djb2 hash of the checkout\n * parameters (`generateIdempotencyKey`). It identifies \"the same checkout\n * attempt\" for the in-flight cache and is never sent to the API.\n * - the **wire key** is the per-attempt UUID (`attemptIdempotencyKey`) that\n * goes out as the `Idempotency-Key` header.\n *\n * The cache is keyed by the lookup key and remembers the wire key it minted.\n * The public helpers accept either key so callers that only ever saw the\n * `idempotencyKey` field keep working unchanged.\n */\n\nimport type { PaymentIntentResponse } from './api/client';\nimport { attemptIdempotencyKey, generateIdempotencyKey } from './api/client';\nimport type { PaymentMethod, ReevitCheckoutConfig } from './types';\nimport { generateReference } from './utils';\n\nconst INTENT_CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\nexport interface IntentIdentityOptions {\n config: ReevitCheckoutConfig;\n method?: PaymentMethod;\n preferredProvider?: string;\n allowedProviders?: string[];\n publicKey?: string;\n}\n\nexport interface IntentCacheEntry {\n promise?: Promise<PaymentIntentResponse>;\n response?: PaymentIntentResponse;\n expiresAt: number;\n reference?: string;\n /** The `Idempotency-Key` sent on the wire for this attempt. */\n idempotencyKey?: string;\n}\n\nconst intentCache = new Map<string, IntentCacheEntry>();\n/** wire key -> lookup key, so callers can pass either one. */\nconst lookupKeyByWireKey = new Map<string, string>();\n\nfunction forgetKey(lookupKey: string): void {\n const entry = intentCache.get(lookupKey);\n if (entry?.idempotencyKey) {\n lookupKeyByWireKey.delete(entry.idempotencyKey);\n }\n intentCache.delete(lookupKey);\n}\n\n/** Accepts either the lookup key or the wire key. */\nfunction toLookupKey(key: string): string {\n return lookupKeyByWireKey.get(key) ?? key;\n}\n\nfunction pruneIntentCache(now: number = Date.now()): void {\n for (const [key, entry] of intentCache) {\n if (entry.expiresAt <= now) {\n forgetKey(key);\n }\n }\n}\n\nfunction getIntentCacheEntryInternal(lookupKey: string): IntentCacheEntry | undefined {\n const entry = intentCache.get(lookupKey);\n if (!entry) {\n return undefined;\n }\n if (entry.expiresAt <= Date.now()) {\n forgetKey(lookupKey);\n return undefined;\n }\n return entry;\n}\n\nfunction setIntentCacheEntryInternal(lookupKey: string, update: Partial<IntentCacheEntry>): IntentCacheEntry {\n const now = Date.now();\n const existing = getIntentCacheEntryInternal(lookupKey);\n const next: IntentCacheEntry = {\n ...existing,\n ...update,\n expiresAt: now + INTENT_CACHE_TTL_MS,\n };\n if (existing?.idempotencyKey && existing.idempotencyKey !== next.idempotencyKey) {\n lookupKeyByWireKey.delete(existing.idempotencyKey);\n }\n intentCache.set(lookupKey, next);\n if (next.idempotencyKey && next.idempotencyKey !== lookupKey) {\n lookupKeyByWireKey.set(next.idempotencyKey, lookupKey);\n }\n return next;\n}\n\nfunction buildIdempotencyPayload(options: IntentIdentityOptions): Record<string, unknown> {\n const { config, method, preferredProvider, allowedProviders, publicKey } = options;\n if (config.sessionSecret) {\n return {\n sessionSecret: config.sessionSecret,\n publicKey: publicKey || config.publicKey || '',\n };\n }\n\n const payload: Record<string, unknown> = {\n amount: config.amount,\n currency: config.currency,\n email: config.email || '',\n phone: config.phone || '',\n customerName: config.customerName || '',\n paymentLinkCode: config.paymentLinkCode || '',\n paymentMethods: config.paymentMethods || [],\n metadata: config.metadata || {},\n customFields: config.customFields || {},\n method: method || '',\n preferredProvider: preferredProvider || '',\n allowedProviders: allowedProviders || [],\n publicKey: publicKey || config.publicKey || '',\n };\n\n if (config.reference) {\n payload.reference = config.reference;\n }\n\n return payload;\n}\n\nexport function resolveIntentIdentity(options: IntentIdentityOptions): {\n /** The value to send as `Idempotency-Key`. */\n idempotencyKey: string;\n /** The local cache key. Never send this on the wire. */\n lookupKey: string;\n reference: string;\n cacheEntry?: IntentCacheEntry;\n} {\n pruneIntentCache();\n\n // A caller-supplied key is authoritative for both roles: the merchant owns\n // its retry semantics and we must send exactly what they asked for.\n const explicitKey = options.config.idempotencyKey;\n const lookupKey = explicitKey || generateIdempotencyKey(buildIdempotencyPayload(options));\n const idempotencyKey = explicitKey || attemptIdempotencyKey(lookupKey);\n\n const existing = getIntentCacheEntryInternal(lookupKey);\n const reference = options.config.reference || existing?.reference || generateReference();\n\n const cacheEntry = setIntentCacheEntryInternal(lookupKey, { reference, idempotencyKey });\n\n return { idempotencyKey, lookupKey, reference, cacheEntry };\n}\n\nexport function getIntentCacheEntry(key: string): IntentCacheEntry | undefined {\n pruneIntentCache();\n return getIntentCacheEntryInternal(toLookupKey(key));\n}\n\nexport function cacheIntentPromise(\n key: string,\n promise: Promise<PaymentIntentResponse>\n): IntentCacheEntry {\n return setIntentCacheEntryInternal(toLookupKey(key), { promise });\n}\n\nexport function cacheIntentResponse(\n key: string,\n response: PaymentIntentResponse\n): IntentCacheEntry {\n return setIntentCacheEntryInternal(toLookupKey(key), { response, promise: undefined });\n}\n\nexport function clearIntentCacheEntry(key: string): void {\n forgetKey(toLookupKey(key));\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACkHA,IAAM,0BAA0B;AAChC,IAAM,kBAAkB;AACxB,IAAI,mCAAmC;AAYhC,SAAS,mBAAmB,UAAoB,WAA2C;AAChG,SAAO;AAAA,IACL,MAAM,UAAU,QAAQ;AAAA,IACxB,SAAS,UAAU,WAAW;AAAA,IAC9B,aAAa,oBAAoB,SAAS,MAAM;AAAA,IAChD,SAAS;AAAA,MACP,YAAY,SAAS;AAAA,MACrB,WAAW,SAAS,QAAQ,IAAI,cAAc,KAAK,SAAS,QAAQ,IAAI,qBAAqB,KAAK;AAAA,MAClG,GAAG,UAAU;AAAA,IACf;AAAA,EACF;AACF;AAEO,SAAS,eAAe,OAAuC;AACpE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,UAAU,SAAS,aAAa;AACxF;AAEO,SAAS,oBAAoB,QAAyB;AAC3D,SAAO,WAAW,OAAO,WAAW,OAAO,WAAW,OAAO,WAAW,OAAO,UAAU;AAC3F;AAkBO,SAAS,uBAAuB,QAAyC;AAE9E,QAAM,aAAa,OAAO,KAAK,MAAM,EAAE,KAAK;AAC5C,QAAM,eAAe,WAClB,IAAI,SAAO,GAAG,GAAG,IAAI,KAAK,UAAU,OAAO,GAAG,CAAC,CAAC,EAAE,EAClD,KAAK,GAAG;AAGX,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,YAAS,QAAQ,KAAK,OAAQ,aAAa,WAAW,CAAC;AACvD,WAAO,OAAO;AAAA,EAChB;AAGA,QAAM,WAAW,SAAS,GAAG,SAAS,EAAE;AAIxC,QAAM,aAAa,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,KAAK,IAAK;AAE1D,SAAO,UAAU,UAAU,IAAI,OAAO;AACxC;AAEA,IAAM,2BAA2B;AAGjC,IAAM,oBAAoB,oBAAI,IAAoB;AAElD,SAAS,kBAAkC;AACzC,MAAI;AACF,UAAM,UAAW,WAA4C;AAC7D,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,GAAG,wBAAwB;AACzC,YAAQ,QAAQ,OAAO,GAAG;AAC1B,YAAQ,WAAW,KAAK;AACxB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,oBAA4B;AAC1C,QAAM,YAAa,WAAmC;AAEtD,MAAI,aAAa,OAAO,UAAU,eAAe,YAAY;AAC3D,QAAI;AACF,aAAO,UAAU,WAAW;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,MAAI,aAAa,OAAO,UAAU,oBAAoB,YAAY;AAChE,cAAU,gBAAgB,KAAK;AAAA,EACjC,OAAO;AACL,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,CAAC,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AAAA,IAC3C;AAAA,EACF;AAEA,QAAM,CAAC,IAAK,MAAM,CAAC,IAAI,KAAQ;AAC/B,QAAM,CAAC,IAAK,MAAM,CAAC,IAAI,KAAQ;AAE/B,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,QAAI,KAAK,MAAM,CAAC,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC;AAAA,EACjD;AAEA,SAAO;AAAA,IACL,IAAI,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE;AAAA,IACvB,IAAI,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE;AAAA,IACvB,IAAI,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE;AAAA,IACvB,IAAI,MAAM,GAAG,EAAE,EAAE,KAAK,EAAE;AAAA,IACxB,IAAI,MAAM,IAAI,EAAE,EAAE,KAAK,EAAE;AAAA,EAC3B,EAAE,KAAK,GAAG;AACZ;AAYO,SAAS,sBAAsB,WAA2B;AAC/D,QAAM,aAAa,GAAG,wBAAwB,GAAG,SAAS;AAC1D,QAAM,QAAQ,gBAAgB;AAE9B,MAAI,OAAO;AACT,QAAI;AACF,YAAMA,YAAW,MAAM,QAAQ,UAAU;AACzC,UAAIA,WAAU;AACZ,eAAOA;AAAA,MACT;AACA,YAAMC,WAAU,kBAAkB;AAClC,YAAM,QAAQ,YAAYA,QAAO;AACjC,aAAOA;AAAA,IACT,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,WAAW,kBAAkB,IAAI,UAAU;AACjD,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AACA,QAAM,UAAU,kBAAkB;AAClC,oBAAkB,IAAI,YAAY,OAAO;AACzC,SAAO;AACT;AAMO,SAAS,8BAAoC;AAClD,oBAAkB,MAAM;AAExB,QAAM,QAAQ,gBAAgB;AAC9B,MAAI,CAAC,OAAO;AACV;AAAA,EACF;AAEA,MAAI;AACF,UAAM,OAAiB,CAAC;AACxB,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,MAAM,MAAM,IAAI,CAAC;AACvB,UAAI,OAAO,IAAI,WAAW,wBAAwB,GAAG;AACnD,aAAK,KAAK,GAAG;AAAA,MACf;AAAA,IACF;AACA,eAAW,OAAO,MAAM;AACtB,YAAM,WAAW,GAAG;AAAA,IACtB;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAKO,IAAM,kBAAN,MAAsB;AAAA,EAK3B,YAAY,QAA+B;AACzC,SAAK,YAAY,OAAO,aAAa;AACrC,SAAK,UAAU,OAAO,WAAW;AACjC,SAAK,UAAU,OAAO,WAAW;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,QACZ,QACA,MACA,MACA,gBAC6B;AAC7B,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO;AAGnE,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,MAChB,mBAAmB;AAAA,MACnB,2BAA2B;AAAA,IAC7B;AACA,QAAI,KAAK,WAAW;AAClB,cAAQ,cAAc,IAAI,KAAK;AAAA,IACjC;AAEA,QAAI,WAAW,UAAU,WAAW,WAAW,WAAW,OAAO;AAG/D,cAAQ,iBAAiB,IAAI,kBAAkB,kBAAkB;AAAA,IACnE;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI,IAAI;AAAA,QACrD;AAAA,QACA;AAAA,QACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,QACpC,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,mBAAa,SAAS;AAEtB,YAAM,eAAe,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAE3D,UAAI,CAAC,SAAS,IAAI;AAChB,eAAO;AAAA,UACL,OAAO,mBAAmB,UAAU,YAAgC;AAAA,QACtE;AAAA,MACF;AAEA,aAAO,EAAE,MAAM,aAAkB;AAAA,IACnC,SAAS,KAAK;AACZ,mBAAa,SAAS;AAEtB,UAAI,eAAe,OAAO;AACxB,YAAI,IAAI,SAAS,cAAc;AAC7B,iBAAO;AAAA,YACL,OAAO;AAAA,cACL,MAAM;AAAA,cACN,SAAS;AAAA,cACT,aAAa;AAAA,YACf;AAAA,UACF;AAAA,QACF;AAEA,YAAI,IAAI,QAAQ,SAAS,iBAAiB,KAAK,IAAI,QAAQ,SAAS,cAAc,GAAG;AACnF,iBAAO;AAAA,YACL,OAAO;AAAA,cACL,MAAM;AAAA,cACN,SAAS;AAAA,cACT,aAAa;AAAA,YACf;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,aAAO;AAAA,QACL,OAAO;AAAA,UACL,MAAM;AAAA,UACN,SAAS;AAAA,UACT,aAAa;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBACJ,QACA,QACA,UAAkB,MAClB,SACiE;AACjE,QACE,KAAK,UAAU,WAAW,WAAW,KACrC,CAAC,oCACD,OAAO,YAAY,aACnB;AACA,yCAAmC;AACnC,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,OAAO,WAAW,YAAY,CAAC,OAAO,UAAU;AACzD,aAAO;AAAA,QACL,OAAO;AAAA,UACL,MAAM;AAAA,UACN,SAAS;AAAA,UACT,aAAa;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAGA,UAAM,WAAoC,EAAE,GAAG,OAAO,SAAS;AAC/D,QAAI,OAAO,OAAO;AAChB,eAAS,iBAAiB,OAAO;AAAA,IACnC;AACA,QAAI,OAAO,OAAO;AAChB,eAAS,iBAAiB,OAAO;AAAA,IACnC;AAEA,UAAM,UAAsC;AAAA,MAC1C,QAAQ,OAAO;AAAA,MACf,UAAU,OAAO;AAAA,MACjB;AAAA,MACA,aAAa,OAAO,SAAU,OAAO,UAAU;AAAA,MAC/C;AAAA,IACF;AAEA,QAAI,QAAQ;AACV,cAAQ,SAAS,KAAK,iBAAiB,MAAM;AAAA,IAC/C;AAEA,QAAI,SAAS,oBAAoB,UAAU,SAAS,kBAAkB,QAAQ;AAC5E,cAAQ,SAAS;AAAA,QACf,QAAQ,SAAS;AAAA,QACjB,mBAAmB,SAAS;AAAA,MAC9B;AAAA,IACF;AAMA,UAAM,iBAAiB,OAAO,kBAAkB,sBAAsB,uBAAuB;AAAA,MAC3F,QAAQ,OAAO;AAAA,MACf,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO,SAAS,OAAO,UAAU,cAAc;AAAA,MACzD,WAAW,OAAO,aAAa;AAAA,MAC/B,QAAQ,UAAU;AAAA,MAClB,UAAU,SAAS,qBAAqB,CAAC,KAAK,SAAS,mBAAmB,CAAC,KAAK;AAAA,MAChF,WAAW,KAAK;AAAA,IAClB,CAAC,CAAC;AAEF,WAAO,KAAK,QAA+B,QAAQ,wBAAwB,SAAS,cAAc;AAAA,EACpG;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,WAAoF;AACzG,WAAO,KAAK,QAA+B,OAAO,gBAAgB,SAAS,EAAE;AAAA,EAC/E;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBAAmB,eAA0F;AACjH,WAAO,KAAK;AAAA,MACV;AAAA,MACA,yBAAyB,mBAAmB,aAAa,CAAC;AAAA,IAC5D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,WAAoF;AACvG,WAAO,KAAK,QAA+B,QAAQ,gBAAgB,SAAS,UAAU;AAAA,EACxF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAqB,WAAmB,cAAuF;AACnI,WAAO,KAAK;AAAA,MACV;AAAA,MACA,gBAAgB,SAAS,iCAAiC,mBAAmB,YAAY,CAAC;AAAA,IAC5F;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBAAoB,WAAoF;AAC5G,WAAO,KAAK,QAA+B,QAAQ,gBAAgB,SAAS,SAAS;AAAA,EACvF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,oBACJ,WACA,cACiE;AACjE,UAAM,QAAQ,eAAe,kBAAkB,mBAAmB,YAAY,CAAC,KAAK;AACpF,WAAO,KAAK,QAA+B,QAAQ,gCAAgC,SAAS,GAAG,KAAK,EAAE;AAAA,EACxG;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,QAA+B;AACtD,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AACF;AAKO,SAAS,mBAAmB,QAAgD;AACjF,SAAO,IAAI,gBAAgB,MAAM;AACnC;;;AC7iBA,IAAM,mBAA2C;AAAA,EAC/C,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAOA,IAAM,0BAA0B,oBAAI,IAAI;AAAA,EACtC;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EACjD;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAC5C,CAAC;AASM,SAAS,iBAAiB,UAA0B;AACzD,QAAM,QAAQ,YAAY,IAAI,YAAY;AAE1C,MAAI;AACF,UAAM,SAAS,IAAI,KAAK,aAAa,MAAM;AAAA,MACzC,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC,EAAE,gBAAgB,EAAE;AAErB,QAAI,OAAO,WAAW,YAAY,OAAO,SAAS,MAAM,GAAG;AACzD,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO,wBAAwB,IAAI,IAAI,IAAI,IAAI;AACjD;AAMO,SAAS,aAAa,OAAe,UAA0B;AACpE,SAAO,KAAK,MAAM,QAAQ,MAAM,iBAAiB,QAAQ,CAAC;AAC5D;AAKO,SAAS,aAAa,QAAgB,UAA0B;AACrE,QAAM,QAAQ,YAAY,IAAI,YAAY;AAC1C,QAAM,WAAW,iBAAiB,IAAI;AACtC,QAAM,YAAY,SAAS,MAAM;AACjC,QAAM,SAAS,iBAAiB,IAAI,KAAK;AAEzC,MAAI;AACF,WAAO,IAAI,KAAK,aAAa,QAAQ;AAAA,MACnC,OAAO;AAAA,MACP,UAAU;AAAA,MACV,uBAAuB;AAAA,MACvB,uBAAuB;AAAA,IACzB,CAAC,EAAE,OAAO,SAAS;AAAA,EACrB,QAAQ;AAEN,WAAO,GAAG,IAAI,IAAI,UAAU,QAAQ,QAAQ,CAAC;AAAA,EAC/C;AACF;AAKO,SAAS,kBAAkB,SAAiB,UAAkB;AACnE,QAAM,YAAY,KAAK,IAAI,EAAE,SAAS,EAAE;AACxC,QAAM,SAAS,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,CAAC;AACxD,SAAO,GAAG,MAAM,IAAI,SAAS,IAAI,MAAM;AACzC;AAKO,SAAS,cAAc,OAAe,UAAkB,MAAe;AAE5E,QAAM,SAAS,MAAM,QAAQ,OAAO,EAAE;AAEtC,QAAM,WAAmC;AAAA,IACvC,IAAI;AAAA;AAAA,IACJ,IAAI;AAAA;AAAA,IACJ,IAAI;AAAA;AAAA,EACN;AAEA,QAAM,UAAU,SAAS,QAAQ,YAAY,CAAC;AAC9C,MAAI,CAAC,QAAS,QAAO,OAAO,UAAU;AAEtC,SAAO,QAAQ,KAAK,MAAM;AAC5B;AAKO,SAAS,YAAY,OAAe,UAAkB,MAAc;AACzE,QAAM,SAAS,MAAM,QAAQ,OAAO,EAAE;AAEtC,MAAI,YAAY,MAAM;AAEpB,QAAI,OAAO,WAAW,KAAK,KAAK,OAAO,WAAW,IAAI;AACpD,YAAM,QAAQ,MAAM,OAAO,MAAM,CAAC;AAClC,aAAO,GAAG,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,MAAM,CAAC,CAAC;AAAA,IACpE;AACA,QAAI,OAAO,WAAW,MAAM,OAAO,WAAW,GAAG,GAAG;AAClD,aAAO,GAAG,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC;AAAA,IACvE;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,cAAc,OAA0C;AACtE,QAAM,SAAS,MAAM,QAAQ,OAAO,EAAE;AAGtC,MAAI;AACJ,MAAI,OAAO,WAAW,KAAK,GAAG;AAC5B,aAAS,OAAO,MAAM,GAAG,CAAC;AAAA,EAC5B,WAAW,OAAO,WAAW,GAAG,GAAG;AACjC,aAAS,OAAO,MAAM,GAAG,CAAC;AAAA,EAC5B,OAAO;AACL,aAAS,OAAO,MAAM,GAAG,CAAC;AAAA,EAC5B;AAGA,QAAM,cAAc,CAAC,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI;AACvD,QAAM,kBAAkB,CAAC,MAAM,IAAI;AACnC,QAAM,qBAAqB,CAAC,MAAM,MAAM,MAAM,IAAI;AAElD,MAAI,YAAY,SAAS,MAAM,EAAG,QAAO;AACzC,MAAI,gBAAgB,SAAS,MAAM,EAAG,QAAO;AAC7C,MAAI,mBAAmB,SAAS,MAAM,EAAG,QAAO;AAEhD,SAAO;AACT;AAKO,SAAS,qBAAqB,OAA4C;AAC/E,QAAM,YAAoC,CAAC;AAG3C,MAAI,MAAM,cAAc;AACtB,cAAU,eAAe,IAAI,MAAM;AAAA,EACrC;AAGA,MAAI,MAAM,wBAAwB;AAChC,cAAU,yBAAyB,IAAI,MAAM;AAC7C,cAAU,gBAAgB,IAAI,MAAM;AAAA,EACtC;AAGA,MAAI,MAAM,uBAAuB;AAC/B,cAAU,kBAAkB,IAAI,MAAM;AACtC,cAAU,wBAAwB,IAAI,MAAM;AAAA,EAC9C;AACA,MAAI,MAAM,iBAAiB;AACzB,cAAU,6BAA6B,IAAI,MAAM;AAAA,EACnD;AAGA,MAAI,MAAM,iBAAiB;AACzB,cAAU,qBAAqB,IAAI,MAAM;AACzC,cAAU,kBAAkB,IAAI,MAAM;AAAA,EACxC;AACA,MAAI,MAAM,cAAc;AACtB,cAAU,kBAAkB,IAAI,MAAM;AAAA,EACxC;AAGA,MAAI,MAAM,aAAa;AACrB,cAAU,iBAAiB,IAAI,MAAM;AAAA,EACvC;AAGA,MAAI,MAAM,WAAW;AACnB,cAAU,eAAe,IAAI,MAAM;AAAA,EACrC;AACA,MAAI,MAAM,gBAAgB;AACxB,cAAU,yBAAyB,IAAI,MAAM;AAAA,EAC/C;AAGA,MAAI,MAAM,cAAc;AACtB,cAAU,iBAAiB,IAAI,MAAM;AACrC,cAAU,oBAAoB,IAAI,MAAM;AACxC,cAAU,oBAAoB,IAAI,MAAM;AAAA,EAC1C;AAGA,MAAI,MAAM,YAAY;AACpB,cAAU,eAAe,IAAI,MAAM;AAAA,EACrC;AAEA,SAAO;AACT;AA+BO,SAAS,MAAM,SAA0D;AAC9E,SAAO,QAAQ,OAAO,OAAO,EAAE,KAAK,GAAG;AACzC;AAKO,SAAS,0BAA0B,UAA0B;AAClE,QAAM,oBAA4C;AAAA,IAChD,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAEA,SAAO,kBAAkB,SAAS,YAAY,CAAC,KAAK;AACtD;;;AC5PA,IAAM,sBAAsB,KAAK,KAAK;AAmBtC,IAAM,cAAc,oBAAI,IAA8B;AAEtD,IAAM,qBAAqB,oBAAI,IAAoB;AAEnD,SAAS,UAAU,WAAyB;AAC1C,QAAM,QAAQ,YAAY,IAAI,SAAS;AACvC,MAAI,OAAO,gBAAgB;AACzB,uBAAmB,OAAO,MAAM,cAAc;AAAA,EAChD;AACA,cAAY,OAAO,SAAS;AAC9B;AAGA,SAAS,YAAY,KAAqB;AACxC,SAAO,mBAAmB,IAAI,GAAG,KAAK;AACxC;AAEA,SAAS,iBAAiB,MAAc,KAAK,IAAI,GAAS;AACxD,aAAW,CAAC,KAAK,KAAK,KAAK,aAAa;AACtC,QAAI,MAAM,aAAa,KAAK;AAC1B,gBAAU,GAAG;AAAA,IACf;AAAA,EACF;AACF;AAEA,SAAS,4BAA4B,WAAiD;AACpF,QAAM,QAAQ,YAAY,IAAI,SAAS;AACvC,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AACA,MAAI,MAAM,aAAa,KAAK,IAAI,GAAG;AACjC,cAAU,SAAS;AACnB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,4BAA4B,WAAmB,QAAqD;AAC3G,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,WAAW,4BAA4B,SAAS;AACtD,QAAM,OAAyB;AAAA,IAC7B,GAAG;AAAA,IACH,GAAG;AAAA,IACH,WAAW,MAAM;AAAA,EACnB;AACA,MAAI,UAAU,kBAAkB,SAAS,mBAAmB,KAAK,gBAAgB;AAC/E,uBAAmB,OAAO,SAAS,cAAc;AAAA,EACnD;AACA,cAAY,IAAI,WAAW,IAAI;AAC/B,MAAI,KAAK,kBAAkB,KAAK,mBAAmB,WAAW;AAC5D,uBAAmB,IAAI,KAAK,gBAAgB,SAAS;AAAA,EACvD;AACA,SAAO;AACT;AAEA,SAAS,wBAAwB,SAAyD;AACxF,QAAM,EAAE,QAAQ,QAAQ,mBAAmB,kBAAkB,UAAU,IAAI;AAC3E,MAAI,OAAO,eAAe;AACxB,WAAO;AAAA,MACL,eAAe,OAAO;AAAA,MACtB,WAAW,aAAa,OAAO,aAAa;AAAA,IAC9C;AAAA,EACF;AAEA,QAAM,UAAmC;AAAA,IACvC,QAAQ,OAAO;AAAA,IACf,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO,SAAS;AAAA,IACvB,OAAO,OAAO,SAAS;AAAA,IACvB,cAAc,OAAO,gBAAgB;AAAA,IACrC,iBAAiB,OAAO,mBAAmB;AAAA,IAC3C,gBAAgB,OAAO,kBAAkB,CAAC;AAAA,IAC1C,UAAU,OAAO,YAAY,CAAC;AAAA,IAC9B,cAAc,OAAO,gBAAgB,CAAC;AAAA,IACtC,QAAQ,UAAU;AAAA,IAClB,mBAAmB,qBAAqB;AAAA,IACxC,kBAAkB,oBAAoB,CAAC;AAAA,IACvC,WAAW,aAAa,OAAO,aAAa;AAAA,EAC9C;AAEA,MAAI,OAAO,WAAW;AACpB,YAAQ,YAAY,OAAO;AAAA,EAC7B;AAEA,SAAO;AACT;AAEO,SAAS,sBAAsB,SAOpC;AACA,mBAAiB;AAIjB,QAAM,cAAc,QAAQ,OAAO;AACnC,QAAM,YAAY,eAAe,uBAAuB,wBAAwB,OAAO,CAAC;AACxF,QAAM,iBAAiB,eAAe,sBAAsB,SAAS;AAErE,QAAM,WAAW,4BAA4B,SAAS;AACtD,QAAM,YAAY,QAAQ,OAAO,aAAa,UAAU,aAAa,kBAAkB;AAEvF,QAAM,aAAa,4BAA4B,WAAW,EAAE,WAAW,eAAe,CAAC;AAEvF,SAAO,EAAE,gBAAgB,WAAW,WAAW,WAAW;AAC5D;AAEO,SAAS,oBAAoB,KAA2C;AAC7E,mBAAiB;AACjB,SAAO,4BAA4B,YAAY,GAAG,CAAC;AACrD;AAEO,SAAS,mBACd,KACA,SACkB;AAClB,SAAO,4BAA4B,YAAY,GAAG,GAAG,EAAE,QAAQ,CAAC;AAClE;AAEO,SAAS,oBACd,KACA,UACkB;AAClB,SAAO,4BAA4B,YAAY,GAAG,GAAG,EAAE,UAAU,SAAS,OAAU,CAAC;AACvF;AAEO,SAAS,sBAAsB,KAAmB;AACvD,YAAU,YAAY,GAAG,CAAC;AAC5B;;;AC7IO,SAAS,qBAAkC;AAChD,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AACF;AAKO,SAAS,cAAc,OAAoB,QAAmC;AACnF,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,WAAW,OAAO,KAAK;AAAA,IACpD,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,QAAQ;AAAA,QACR,eAAe,OAAO;AAAA,QACtB,gBACE,OAAO,QAAQ,kBAAkB,WAAW,IAAI,OAAO,QAAQ,iBAAiB,CAAC,IAAI;AAAA,MACzF;AAAA,IACF,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,UAAU,OAAO,OAAO,QAAQ;AAAA,IAC7D,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,mBAAmB,gBAAgB,OAAO,QAAQ;AAAA,IAC/E,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,cAAc,OAAO,KAAK;AAAA,IACvD,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,WAAW,QAAQ,OAAO,QAAQ;AAAA,IAC/D,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,UAAU,OAAO,OAAO,QAAQ;AAAA,IAC7D,KAAK;AACH,aAAO,EAAE,GAAG,mBAAmB,GAAG,QAAQ,SAAS,eAAe,MAAM,cAAc;AAAA,IACxF,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,SAAS;AAAA,IACtC;AACE,aAAO;AAAA,EACX;AACF;","names":["existing","created"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
// src/api/client.ts
|
|
2
2
|
var API_BASE_URL_PRODUCTION = "https://api.reevit.io";
|
|
3
3
|
var DEFAULT_TIMEOUT = 3e4;
|
|
4
|
+
var hasWarnedAboutLiveBrowserIntents = false;
|
|
4
5
|
function createPaymentError(response, errorData) {
|
|
5
6
|
return {
|
|
6
7
|
code: errorData.code || "api_error",
|
|
7
8
|
message: errorData.message || "An unexpected error occurred",
|
|
9
|
+
recoverable: isRecoverableStatus(response.status),
|
|
8
10
|
details: {
|
|
9
11
|
httpStatus: response.status,
|
|
12
|
+
requestId: response.headers.get("x-request-id") || response.headers.get("x-reevit-request-id") || void 0,
|
|
10
13
|
...errorData.details
|
|
11
14
|
}
|
|
12
15
|
};
|
|
13
16
|
}
|
|
17
|
+
function isPaymentError(error) {
|
|
18
|
+
return typeof error === "object" && error !== null && "code" in error && "message" in error;
|
|
19
|
+
}
|
|
20
|
+
function isRecoverableStatus(status) {
|
|
21
|
+
return status === 408 || status === 409 || status === 425 || status === 429 || status >= 500;
|
|
22
|
+
}
|
|
14
23
|
function generateIdempotencyKey(params) {
|
|
15
24
|
const sortedKeys = Object.keys(params).sort();
|
|
16
25
|
const stableString = sortedKeys.map((key) => `${key}:${JSON.stringify(params[key])}`).join("|");
|
|
@@ -23,6 +32,95 @@ function generateIdempotencyKey(params) {
|
|
|
23
32
|
const timeBucket = Math.floor(Date.now() / (5 * 60 * 1e3));
|
|
24
33
|
return `reevit_${timeBucket}_${hashHex}`;
|
|
25
34
|
}
|
|
35
|
+
var IDEMPOTENCY_STORE_PREFIX = "reevit:idem:";
|
|
36
|
+
var memoryAttemptKeys = /* @__PURE__ */ new Map();
|
|
37
|
+
function getSessionStore() {
|
|
38
|
+
try {
|
|
39
|
+
const storage = globalThis.sessionStorage;
|
|
40
|
+
if (!storage) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const probe = `${IDEMPOTENCY_STORE_PREFIX}probe`;
|
|
44
|
+
storage.setItem(probe, "1");
|
|
45
|
+
storage.removeItem(probe);
|
|
46
|
+
return storage;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function newIdempotencyKey() {
|
|
52
|
+
const cryptoObj = globalThis.crypto;
|
|
53
|
+
if (cryptoObj && typeof cryptoObj.randomUUID === "function") {
|
|
54
|
+
try {
|
|
55
|
+
return cryptoObj.randomUUID();
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const bytes = new Uint8Array(16);
|
|
60
|
+
if (cryptoObj && typeof cryptoObj.getRandomValues === "function") {
|
|
61
|
+
cryptoObj.getRandomValues(bytes);
|
|
62
|
+
} else {
|
|
63
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
64
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
68
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
69
|
+
const hex = [];
|
|
70
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
71
|
+
hex.push(bytes[i].toString(16).padStart(2, "0"));
|
|
72
|
+
}
|
|
73
|
+
return [
|
|
74
|
+
hex.slice(0, 4).join(""),
|
|
75
|
+
hex.slice(4, 6).join(""),
|
|
76
|
+
hex.slice(6, 8).join(""),
|
|
77
|
+
hex.slice(8, 10).join(""),
|
|
78
|
+
hex.slice(10, 16).join("")
|
|
79
|
+
].join("-");
|
|
80
|
+
}
|
|
81
|
+
function attemptIdempotencyKey(lookupKey) {
|
|
82
|
+
const storageKey = `${IDEMPOTENCY_STORE_PREFIX}${lookupKey}`;
|
|
83
|
+
const store = getSessionStore();
|
|
84
|
+
if (store) {
|
|
85
|
+
try {
|
|
86
|
+
const existing2 = store.getItem(storageKey);
|
|
87
|
+
if (existing2) {
|
|
88
|
+
return existing2;
|
|
89
|
+
}
|
|
90
|
+
const created2 = newIdempotencyKey();
|
|
91
|
+
store.setItem(storageKey, created2);
|
|
92
|
+
return created2;
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const existing = memoryAttemptKeys.get(storageKey);
|
|
97
|
+
if (existing) {
|
|
98
|
+
return existing;
|
|
99
|
+
}
|
|
100
|
+
const created = newIdempotencyKey();
|
|
101
|
+
memoryAttemptKeys.set(storageKey, created);
|
|
102
|
+
return created;
|
|
103
|
+
}
|
|
104
|
+
function clearIdempotencyAttemptKeys() {
|
|
105
|
+
memoryAttemptKeys.clear();
|
|
106
|
+
const store = getSessionStore();
|
|
107
|
+
if (!store) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const keys = [];
|
|
112
|
+
for (let i = 0; i < store.length; i++) {
|
|
113
|
+
const key = store.key(i);
|
|
114
|
+
if (key && key.startsWith(IDEMPOTENCY_STORE_PREFIX)) {
|
|
115
|
+
keys.push(key);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const key of keys) {
|
|
119
|
+
store.removeItem(key);
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
}
|
|
26
124
|
var ReevitAPIClient = class {
|
|
27
125
|
constructor(config) {
|
|
28
126
|
this.publicKey = config.publicKey || "";
|
|
@@ -39,13 +137,13 @@ var ReevitAPIClient = class {
|
|
|
39
137
|
const headers = {
|
|
40
138
|
"Content-Type": "application/json",
|
|
41
139
|
"X-Reevit-Client": "@reevit/core",
|
|
42
|
-
"X-Reevit-Client-Version": "0.
|
|
140
|
+
"X-Reevit-Client-Version": "0.9.1"
|
|
43
141
|
};
|
|
44
142
|
if (this.publicKey) {
|
|
45
143
|
headers["X-Reevit-Key"] = this.publicKey;
|
|
46
144
|
}
|
|
47
145
|
if (method === "POST" || method === "PATCH" || method === "PUT") {
|
|
48
|
-
headers["Idempotency-Key"] = idempotencyKey || (
|
|
146
|
+
headers["Idempotency-Key"] = idempotencyKey || newIdempotencyKey();
|
|
49
147
|
}
|
|
50
148
|
try {
|
|
51
149
|
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
@@ -69,7 +167,8 @@ var ReevitAPIClient = class {
|
|
|
69
167
|
return {
|
|
70
168
|
error: {
|
|
71
169
|
code: "request_timeout",
|
|
72
|
-
message: "The request timed out. Please try again."
|
|
170
|
+
message: "The request timed out. Please try again.",
|
|
171
|
+
recoverable: true
|
|
73
172
|
}
|
|
74
173
|
};
|
|
75
174
|
}
|
|
@@ -77,7 +176,8 @@ var ReevitAPIClient = class {
|
|
|
77
176
|
return {
|
|
78
177
|
error: {
|
|
79
178
|
code: "network_error",
|
|
80
|
-
message: "Unable to connect to Reevit. Please check your internet connection."
|
|
179
|
+
message: "Unable to connect to Reevit. Please check your internet connection.",
|
|
180
|
+
recoverable: true
|
|
81
181
|
}
|
|
82
182
|
};
|
|
83
183
|
}
|
|
@@ -85,7 +185,8 @@ var ReevitAPIClient = class {
|
|
|
85
185
|
return {
|
|
86
186
|
error: {
|
|
87
187
|
code: "unknown_error",
|
|
88
|
-
message: "An unexpected error occurred. Please try again."
|
|
188
|
+
message: "An unexpected error occurred. Please try again.",
|
|
189
|
+
recoverable: true
|
|
89
190
|
}
|
|
90
191
|
};
|
|
91
192
|
}
|
|
@@ -94,6 +195,21 @@ var ReevitAPIClient = class {
|
|
|
94
195
|
* Creates a payment intent
|
|
95
196
|
*/
|
|
96
197
|
async createPaymentIntent(config, method, country = "GH", options) {
|
|
198
|
+
if (this.publicKey.startsWith("pfk_live_") && !hasWarnedAboutLiveBrowserIntents && typeof console !== "undefined") {
|
|
199
|
+
hasWarnedAboutLiveBrowserIntents = true;
|
|
200
|
+
console.warn(
|
|
201
|
+
"Creating live payment intents from the browser is deprecated. Create a checkout session on your server and pass sessionSecret to the browser SDK instead."
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if (typeof config.amount !== "number" || !config.currency) {
|
|
205
|
+
return {
|
|
206
|
+
error: {
|
|
207
|
+
code: "invalid_checkout_config",
|
|
208
|
+
message: "amount and currency are required when creating a payment intent in the browser.",
|
|
209
|
+
recoverable: false
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
97
213
|
const metadata = { ...config.metadata };
|
|
98
214
|
if (config.email) {
|
|
99
215
|
metadata.customer_email = config.email;
|
|
@@ -117,7 +233,7 @@ var ReevitAPIClient = class {
|
|
|
117
233
|
allowed_providers: options?.allowedProviders
|
|
118
234
|
};
|
|
119
235
|
}
|
|
120
|
-
const idempotencyKey = config.idempotencyKey || generateIdempotencyKey({
|
|
236
|
+
const idempotencyKey = config.idempotencyKey || attemptIdempotencyKey(generateIdempotencyKey({
|
|
121
237
|
amount: config.amount,
|
|
122
238
|
currency: config.currency,
|
|
123
239
|
customer: config.email || config.metadata?.customerId || "",
|
|
@@ -125,7 +241,7 @@ var ReevitAPIClient = class {
|
|
|
125
241
|
method: method || "",
|
|
126
242
|
provider: options?.preferredProviders?.[0] || options?.allowedProviders?.[0] || "",
|
|
127
243
|
publicKey: this.publicKey
|
|
128
|
-
});
|
|
244
|
+
}));
|
|
129
245
|
return this.request("POST", "/v1/payments/intents", request, idempotencyKey);
|
|
130
246
|
}
|
|
131
247
|
/**
|
|
@@ -134,6 +250,15 @@ var ReevitAPIClient = class {
|
|
|
134
250
|
async getPaymentIntent(paymentId) {
|
|
135
251
|
return this.request("GET", `/v1/payments/${paymentId}`);
|
|
136
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Retrieves a server-created checkout session using its public session secret.
|
|
255
|
+
*/
|
|
256
|
+
async getCheckoutSession(sessionSecret) {
|
|
257
|
+
return this.request(
|
|
258
|
+
"GET",
|
|
259
|
+
`/v1/checkout/sessions/${encodeURIComponent(sessionSecret)}`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
137
262
|
/**
|
|
138
263
|
* Confirms a payment after PSP callback
|
|
139
264
|
*/
|
|
@@ -185,25 +310,62 @@ function createReevitClient(config) {
|
|
|
185
310
|
}
|
|
186
311
|
|
|
187
312
|
// src/utils.ts
|
|
313
|
+
var CURRENCY_LOCALES = {
|
|
314
|
+
GHS: "en-GH",
|
|
315
|
+
NGN: "en-NG",
|
|
316
|
+
KES: "en-KE",
|
|
317
|
+
USD: "en-US",
|
|
318
|
+
EUR: "de-DE",
|
|
319
|
+
GBP: "en-GB"
|
|
320
|
+
};
|
|
321
|
+
var ZERO_DECIMAL_CURRENCIES = /* @__PURE__ */ new Set([
|
|
322
|
+
"XOF",
|
|
323
|
+
"XAF",
|
|
324
|
+
"RWF",
|
|
325
|
+
"UGX",
|
|
326
|
+
"JPY",
|
|
327
|
+
"KRW",
|
|
328
|
+
"BIF",
|
|
329
|
+
"GNF",
|
|
330
|
+
"VND",
|
|
331
|
+
"CLP",
|
|
332
|
+
"ISK",
|
|
333
|
+
"KMF",
|
|
334
|
+
"DJF",
|
|
335
|
+
"PYG",
|
|
336
|
+
"MGA"
|
|
337
|
+
]);
|
|
338
|
+
function currencyExponent(currency) {
|
|
339
|
+
const code = (currency || "").toUpperCase();
|
|
340
|
+
try {
|
|
341
|
+
const digits = new Intl.NumberFormat("en", {
|
|
342
|
+
style: "currency",
|
|
343
|
+
currency: code
|
|
344
|
+
}).resolvedOptions().maximumFractionDigits;
|
|
345
|
+
if (typeof digits === "number" && Number.isFinite(digits)) {
|
|
346
|
+
return digits;
|
|
347
|
+
}
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
350
|
+
return ZERO_DECIMAL_CURRENCIES.has(code) ? 0 : 2;
|
|
351
|
+
}
|
|
352
|
+
function toMinorUnits(major, currency) {
|
|
353
|
+
return Math.round(major * 10 ** currencyExponent(currency));
|
|
354
|
+
}
|
|
188
355
|
function formatAmount(amount, currency) {
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
KES: { locale: "en-KE", minimumFractionDigits: 2 },
|
|
194
|
-
USD: { locale: "en-US", minimumFractionDigits: 2 },
|
|
195
|
-
EUR: { locale: "de-DE", minimumFractionDigits: 2 },
|
|
196
|
-
GBP: { locale: "en-GB", minimumFractionDigits: 2 }
|
|
197
|
-
};
|
|
198
|
-
const format = currencyFormats[currency.toUpperCase()] || { locale: "en-US", minimumFractionDigits: 2 };
|
|
356
|
+
const code = (currency || "").toUpperCase();
|
|
357
|
+
const exponent = currencyExponent(code);
|
|
358
|
+
const majorUnit = amount / 10 ** exponent;
|
|
359
|
+
const locale = CURRENCY_LOCALES[code] || "en-US";
|
|
199
360
|
try {
|
|
200
|
-
return new Intl.NumberFormat(
|
|
361
|
+
return new Intl.NumberFormat(locale, {
|
|
201
362
|
style: "currency",
|
|
202
|
-
currency:
|
|
203
|
-
minimumFractionDigits:
|
|
363
|
+
currency: code,
|
|
364
|
+
minimumFractionDigits: exponent,
|
|
365
|
+
maximumFractionDigits: exponent
|
|
204
366
|
}).format(majorUnit);
|
|
205
367
|
} catch {
|
|
206
|
-
return `${
|
|
368
|
+
return `${code} ${majorUnit.toFixed(exponent)}`;
|
|
207
369
|
}
|
|
208
370
|
}
|
|
209
371
|
function generateReference(prefix = "reevit") {
|
|
@@ -321,37 +483,60 @@ function detectCountryFromCurrency(currency) {
|
|
|
321
483
|
// src/intent.ts
|
|
322
484
|
var INTENT_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
323
485
|
var intentCache = /* @__PURE__ */ new Map();
|
|
486
|
+
var lookupKeyByWireKey = /* @__PURE__ */ new Map();
|
|
487
|
+
function forgetKey(lookupKey) {
|
|
488
|
+
const entry = intentCache.get(lookupKey);
|
|
489
|
+
if (entry?.idempotencyKey) {
|
|
490
|
+
lookupKeyByWireKey.delete(entry.idempotencyKey);
|
|
491
|
+
}
|
|
492
|
+
intentCache.delete(lookupKey);
|
|
493
|
+
}
|
|
494
|
+
function toLookupKey(key) {
|
|
495
|
+
return lookupKeyByWireKey.get(key) ?? key;
|
|
496
|
+
}
|
|
324
497
|
function pruneIntentCache(now = Date.now()) {
|
|
325
498
|
for (const [key, entry] of intentCache) {
|
|
326
499
|
if (entry.expiresAt <= now) {
|
|
327
|
-
|
|
500
|
+
forgetKey(key);
|
|
328
501
|
}
|
|
329
502
|
}
|
|
330
503
|
}
|
|
331
|
-
function getIntentCacheEntryInternal(
|
|
332
|
-
const entry = intentCache.get(
|
|
504
|
+
function getIntentCacheEntryInternal(lookupKey) {
|
|
505
|
+
const entry = intentCache.get(lookupKey);
|
|
333
506
|
if (!entry) {
|
|
334
507
|
return void 0;
|
|
335
508
|
}
|
|
336
509
|
if (entry.expiresAt <= Date.now()) {
|
|
337
|
-
|
|
510
|
+
forgetKey(lookupKey);
|
|
338
511
|
return void 0;
|
|
339
512
|
}
|
|
340
513
|
return entry;
|
|
341
514
|
}
|
|
342
|
-
function setIntentCacheEntryInternal(
|
|
515
|
+
function setIntentCacheEntryInternal(lookupKey, update) {
|
|
343
516
|
const now = Date.now();
|
|
344
|
-
const existing = getIntentCacheEntryInternal(
|
|
517
|
+
const existing = getIntentCacheEntryInternal(lookupKey);
|
|
345
518
|
const next = {
|
|
346
519
|
...existing,
|
|
347
520
|
...update,
|
|
348
521
|
expiresAt: now + INTENT_CACHE_TTL_MS
|
|
349
522
|
};
|
|
350
|
-
|
|
523
|
+
if (existing?.idempotencyKey && existing.idempotencyKey !== next.idempotencyKey) {
|
|
524
|
+
lookupKeyByWireKey.delete(existing.idempotencyKey);
|
|
525
|
+
}
|
|
526
|
+
intentCache.set(lookupKey, next);
|
|
527
|
+
if (next.idempotencyKey && next.idempotencyKey !== lookupKey) {
|
|
528
|
+
lookupKeyByWireKey.set(next.idempotencyKey, lookupKey);
|
|
529
|
+
}
|
|
351
530
|
return next;
|
|
352
531
|
}
|
|
353
532
|
function buildIdempotencyPayload(options) {
|
|
354
533
|
const { config, method, preferredProvider, allowedProviders, publicKey } = options;
|
|
534
|
+
if (config.sessionSecret) {
|
|
535
|
+
return {
|
|
536
|
+
sessionSecret: config.sessionSecret,
|
|
537
|
+
publicKey: publicKey || config.publicKey || ""
|
|
538
|
+
};
|
|
539
|
+
}
|
|
355
540
|
const payload = {
|
|
356
541
|
amount: config.amount,
|
|
357
542
|
currency: config.currency,
|
|
@@ -374,24 +559,26 @@ function buildIdempotencyPayload(options) {
|
|
|
374
559
|
}
|
|
375
560
|
function resolveIntentIdentity(options) {
|
|
376
561
|
pruneIntentCache();
|
|
377
|
-
const
|
|
378
|
-
const
|
|
562
|
+
const explicitKey = options.config.idempotencyKey;
|
|
563
|
+
const lookupKey = explicitKey || generateIdempotencyKey(buildIdempotencyPayload(options));
|
|
564
|
+
const idempotencyKey = explicitKey || attemptIdempotencyKey(lookupKey);
|
|
565
|
+
const existing = getIntentCacheEntryInternal(lookupKey);
|
|
379
566
|
const reference = options.config.reference || existing?.reference || generateReference();
|
|
380
|
-
const cacheEntry = setIntentCacheEntryInternal(
|
|
381
|
-
return { idempotencyKey, reference, cacheEntry };
|
|
567
|
+
const cacheEntry = setIntentCacheEntryInternal(lookupKey, { reference, idempotencyKey });
|
|
568
|
+
return { idempotencyKey, lookupKey, reference, cacheEntry };
|
|
382
569
|
}
|
|
383
|
-
function getIntentCacheEntry(
|
|
570
|
+
function getIntentCacheEntry(key) {
|
|
384
571
|
pruneIntentCache();
|
|
385
|
-
return getIntentCacheEntryInternal(
|
|
572
|
+
return getIntentCacheEntryInternal(toLookupKey(key));
|
|
386
573
|
}
|
|
387
|
-
function cacheIntentPromise(
|
|
388
|
-
return setIntentCacheEntryInternal(
|
|
574
|
+
function cacheIntentPromise(key, promise) {
|
|
575
|
+
return setIntentCacheEntryInternal(toLookupKey(key), { promise });
|
|
389
576
|
}
|
|
390
|
-
function cacheIntentResponse(
|
|
391
|
-
return setIntentCacheEntryInternal(
|
|
577
|
+
function cacheIntentResponse(key, response) {
|
|
578
|
+
return setIntentCacheEntryInternal(toLookupKey(key), { response, promise: void 0 });
|
|
392
579
|
}
|
|
393
|
-
function clearIntentCacheEntry(
|
|
394
|
-
|
|
580
|
+
function clearIntentCacheEntry(key) {
|
|
581
|
+
forgetKey(toLookupKey(key));
|
|
395
582
|
}
|
|
396
583
|
|
|
397
584
|
// src/state.ts
|
|
@@ -435,13 +622,17 @@ function reevitReducer(state, action) {
|
|
|
435
622
|
}
|
|
436
623
|
export {
|
|
437
624
|
ReevitAPIClient,
|
|
625
|
+
attemptIdempotencyKey,
|
|
438
626
|
cacheIntentPromise,
|
|
439
627
|
cacheIntentResponse,
|
|
628
|
+
clearIdempotencyAttemptKeys,
|
|
440
629
|
clearIntentCacheEntry,
|
|
441
630
|
cn,
|
|
442
631
|
createInitialState,
|
|
632
|
+
createPaymentError,
|
|
443
633
|
createReevitClient,
|
|
444
634
|
createThemeVariables,
|
|
635
|
+
currencyExponent,
|
|
445
636
|
detectCountryFromCurrency,
|
|
446
637
|
detectNetwork,
|
|
447
638
|
formatAmount,
|
|
@@ -449,8 +640,11 @@ export {
|
|
|
449
640
|
generateIdempotencyKey,
|
|
450
641
|
generateReference,
|
|
451
642
|
getIntentCacheEntry,
|
|
643
|
+
isPaymentError,
|
|
644
|
+
newIdempotencyKey,
|
|
452
645
|
reevitReducer,
|
|
453
646
|
resolveIntentIdentity,
|
|
647
|
+
toMinorUnits,
|
|
454
648
|
validatePhone
|
|
455
649
|
};
|
|
456
650
|
//# sourceMappingURL=index.mjs.map
|