@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/README.md
CHANGED
|
@@ -8,6 +8,25 @@ The foundation for all Reevit payment SDKs. It provides the shared API client, s
|
|
|
8
8
|
npm install @reevit/core
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Compatibility
|
|
12
|
+
|
|
13
|
+
`@reevit/core` is a regular `dependencies` entry of each framework SDK, so it is
|
|
14
|
+
installed for you. Install it directly only when you are building against the
|
|
15
|
+
low-level API.
|
|
16
|
+
|
|
17
|
+
| `@reevit/core` | Required by |
|
|
18
|
+
|---|---|
|
|
19
|
+
| 0.9.x | `@reevit/react` 0.9.x–0.10.x, `@reevit/vue` 0.9.x–0.10.x, `@reevit/svelte` 0.9.x–0.10.x |
|
|
20
|
+
|
|
21
|
+
0.9.1 is a patch release on purpose: it carries the idempotency-key and
|
|
22
|
+
zero-decimal-currency fixes, and the framework SDKs pick it up through their
|
|
23
|
+
existing `^0.9.0` range without a coordinated bump.
|
|
24
|
+
|
|
25
|
+
On a `0.x` package a caret range pins the **minor**, not the major:
|
|
26
|
+
`^0.9.0` resolves to `>=0.9.0 <0.10.0`. A future `@reevit/core` 0.10.0 is
|
|
27
|
+
therefore not picked up automatically — the React, Vue and Svelte manifests
|
|
28
|
+
have to be bumped together in the same release.
|
|
29
|
+
|
|
11
30
|
## Features
|
|
12
31
|
|
|
13
32
|
- **ReevitAPIClient**: A lightweight, promise-based client for interacting with the Reevit backend.
|
|
@@ -42,16 +61,62 @@ if (data) {
|
|
|
42
61
|
}
|
|
43
62
|
```
|
|
44
63
|
|
|
64
|
+
### Loading a checkout session
|
|
65
|
+
|
|
66
|
+
Browser SDKs should prefer server-created checkout sessions. Use the session secret returned by your backend to load the payment intent without exposing private API credentials.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const { data, error } = await client.getCheckoutSession('cs_session_secret');
|
|
70
|
+
|
|
71
|
+
if (data) {
|
|
72
|
+
console.log('Ready to render:', data.payment_intent.id);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Error handling
|
|
77
|
+
|
|
78
|
+
Core returns a consistent `{ data, error }` result. Errors include `code`, `message`, `recoverable`, and `details.httpStatus` when the API returns a status code.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const result = await client.getCheckoutSession('cs_session_secret');
|
|
82
|
+
|
|
83
|
+
if (result.error) {
|
|
84
|
+
if (result.error.recoverable) {
|
|
85
|
+
// show retry UI
|
|
86
|
+
}
|
|
87
|
+
console.error(result.error.code, result.error.message);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
45
91
|
### Using Utilities
|
|
46
92
|
|
|
47
93
|
```typescript
|
|
48
94
|
import { formatAmount, validatePhone, detectNetwork } from '@reevit/core';
|
|
49
95
|
|
|
50
|
-
console.log(formatAmount(10000, 'GHS')); // "GH₵
|
|
96
|
+
console.log(formatAmount(10000, 'GHS')); // "GH₵100.00"
|
|
51
97
|
console.log(validatePhone('0241234567')); // true
|
|
52
98
|
console.log(detectNetwork('0241234567')); // "mtn"
|
|
53
99
|
```
|
|
54
100
|
|
|
101
|
+
### Amounts and currency exponents
|
|
102
|
+
|
|
103
|
+
Amounts are always integers in the smallest unit of the currency, and that unit
|
|
104
|
+
is **not** always 1/100. GHS, NGN and USD have two decimals; XOF, XAF, RWF, UGX,
|
|
105
|
+
JPY and KRW have none — 5,000 XOF is 5000, not 500000.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { currencyExponent, formatAmount, toMinorUnits } from '@reevit/core';
|
|
109
|
+
|
|
110
|
+
currencyExponent('GHS'); // 2
|
|
111
|
+
currencyExponent('XOF'); // 0
|
|
112
|
+
|
|
113
|
+
formatAmount(4500, 'GHS'); // "GH₵45.00"
|
|
114
|
+
formatAmount(5000, 'XOF'); // "F CFA 5,000" (not "XOF 50.00")
|
|
115
|
+
|
|
116
|
+
toMinorUnits(45, 'GHS'); // 4500
|
|
117
|
+
toMinorUnits(5000, 'XOF'); // 5000
|
|
118
|
+
```
|
|
119
|
+
|
|
55
120
|
### Intent Identity & Idempotency
|
|
56
121
|
|
|
57
122
|
Core exports helpers to stabilize intent creation and dedupe in-flight requests.
|
|
@@ -59,7 +124,7 @@ Core exports helpers to stabilize intent creation and dedupe in-flight requests.
|
|
|
59
124
|
```typescript
|
|
60
125
|
import { resolveIntentIdentity } from '@reevit/core';
|
|
61
126
|
|
|
62
|
-
const { idempotencyKey, reference } = resolveIntentIdentity({
|
|
127
|
+
const { idempotencyKey, lookupKey, reference } = resolveIntentIdentity({
|
|
63
128
|
config: {
|
|
64
129
|
amount: 5000,
|
|
65
130
|
currency: 'GHS',
|
|
@@ -70,9 +135,35 @@ const { idempotencyKey, reference } = resolveIntentIdentity({
|
|
|
70
135
|
});
|
|
71
136
|
```
|
|
72
137
|
|
|
138
|
+
**Pass your own order-scoped `idempotencyKey` for retry safety across page
|
|
139
|
+
loads.** Without one, the SDK generates a per-tab attempt key: a UUID minted on
|
|
140
|
+
the first request and kept in `sessionStorage`, so a repeated "Continue" click
|
|
141
|
+
in the same tab is deduped by the API, while a reload or a different shopper
|
|
142
|
+
starts a new attempt.
|
|
143
|
+
|
|
144
|
+
Two keys are in play and they must not be confused:
|
|
145
|
+
|
|
146
|
+
| | Value | Where it goes |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `idempotencyKey` | UUID from `newIdempotencyKey()` / `attemptIdempotencyKey()`, or the one you supplied | the `Idempotency-Key` request header |
|
|
149
|
+
| `lookupKey` | deterministic djb2 hash from `generateIdempotencyKey()` | local in-flight cache only — **never** the wire |
|
|
150
|
+
|
|
151
|
+
`generateIdempotencyKey()` stays exported for existing callers, but a 32-bit
|
|
152
|
+
hash is not safe as a wire key: two unrelated shoppers can collide and be handed
|
|
153
|
+
each other's `client_secret`. Use `newIdempotencyKey()` if you need to mint one
|
|
154
|
+
yourself, and `clearIdempotencyAttemptKeys()` to start a fresh attempt after a
|
|
155
|
+
completed checkout.
|
|
156
|
+
|
|
73
157
|
## Release Notes
|
|
74
158
|
|
|
75
|
-
### v0.
|
|
159
|
+
### v0.9.1
|
|
160
|
+
|
|
161
|
+
- The wire `Idempotency-Key` is now a per-attempt UUID instead of a djb2 hash
|
|
162
|
+
- Zero-decimal currencies (XOF, XAF, RWF, UGX, JPY, …) are no longer divided by 100
|
|
163
|
+
- Added `currencyExponent`, `toMinorUnits`, `newIdempotencyKey`, `attemptIdempotencyKey`, `clearIdempotencyAttemptKeys`
|
|
164
|
+
- First test suite for this package; CI runs `npm test`
|
|
165
|
+
|
|
166
|
+
### v0.9.0
|
|
76
167
|
|
|
77
168
|
- Version alignment across all Reevit SDKs
|
|
78
169
|
- Updated shared CSS with redesigned checkout visual system
|
package/dist/index.d.mts
CHANGED
|
@@ -10,10 +10,12 @@ type PaymentSource = 'payment_link' | 'api' | 'subscription';
|
|
|
10
10
|
interface ReevitCheckoutConfig {
|
|
11
11
|
/** Your Reevit public key (required for API-created intents; omit for payment links) */
|
|
12
12
|
publicKey?: string;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
|
|
13
|
+
/** Server-created checkout session secret. Prefer this for browser checkouts. */
|
|
14
|
+
sessionSecret?: string;
|
|
15
|
+
/** Amount in the smallest currency unit (e.g., pesewas for GHS). Required unless sessionSecret or initialPaymentIntent is provided. */
|
|
16
|
+
amount?: number;
|
|
17
|
+
/** Currency code (e.g., 'GHS', 'NGN', 'USD'). Required unless sessionSecret or initialPaymentIntent is provided. */
|
|
18
|
+
currency?: string;
|
|
17
19
|
/** Customer email address */
|
|
18
20
|
email?: string;
|
|
19
21
|
/** Customer phone number (required for mobile money) */
|
|
@@ -247,6 +249,7 @@ interface PaymentIntentResponse {
|
|
|
247
249
|
provider_ref_id?: string;
|
|
248
250
|
status: string;
|
|
249
251
|
client_secret: string;
|
|
252
|
+
session_secret?: string;
|
|
250
253
|
psp_public_key: string;
|
|
251
254
|
psp_credentials?: {
|
|
252
255
|
merchantAccount?: string | number;
|
|
@@ -267,6 +270,13 @@ interface PaymentIntentResponse {
|
|
|
267
270
|
}>;
|
|
268
271
|
branding?: Record<string, unknown>;
|
|
269
272
|
}
|
|
273
|
+
interface CheckoutSessionResponse {
|
|
274
|
+
id: string;
|
|
275
|
+
client_secret: string;
|
|
276
|
+
session_secret: string;
|
|
277
|
+
payment_intent: PaymentIntentResponse;
|
|
278
|
+
expires_at?: string;
|
|
279
|
+
}
|
|
270
280
|
interface ConfirmPaymentRequest {
|
|
271
281
|
provider_ref_id: string;
|
|
272
282
|
provider_data?: Record<string, unknown>;
|
|
@@ -298,8 +308,15 @@ interface PaymentDetailResponse {
|
|
|
298
308
|
interface APIErrorResponse {
|
|
299
309
|
code: string;
|
|
300
310
|
message: string;
|
|
301
|
-
details?: Record<string,
|
|
311
|
+
details?: Record<string, unknown>;
|
|
302
312
|
}
|
|
313
|
+
type ReevitAPIResult<T> = {
|
|
314
|
+
data: T;
|
|
315
|
+
error?: never;
|
|
316
|
+
} | {
|
|
317
|
+
data?: never;
|
|
318
|
+
error: PaymentError;
|
|
319
|
+
};
|
|
303
320
|
interface ReevitAPIClientConfig {
|
|
304
321
|
/** Your Reevit public key */
|
|
305
322
|
publicKey?: string;
|
|
@@ -309,11 +326,48 @@ interface ReevitAPIClientConfig {
|
|
|
309
326
|
timeout?: number;
|
|
310
327
|
}
|
|
311
328
|
/**
|
|
312
|
-
*
|
|
313
|
-
|
|
314
|
-
|
|
329
|
+
* Creates a PaymentError from an API error response
|
|
330
|
+
*/
|
|
331
|
+
declare function createPaymentError(response: Response, errorData: APIErrorResponse): PaymentError;
|
|
332
|
+
declare function isPaymentError(error: unknown): error is PaymentError;
|
|
333
|
+
/**
|
|
334
|
+
* Generates a deterministic **cache/lookup** key from input parameters.
|
|
335
|
+
*
|
|
336
|
+
* NEVER SEND THIS ON THE WIRE. It is a 32-bit djb2 hash bucketed into
|
|
337
|
+
* 5-minute windows, so two unrelated shoppers can collide and be handed each
|
|
338
|
+
* other's payment intent (and therefore each other's `client_secret`), and a
|
|
339
|
+
* shopper legitimately buying the same item twice inside one window would be
|
|
340
|
+
* charged once. Its only job is to identify "the same checkout attempt" inside
|
|
341
|
+
* a single browser tab so the in-flight intent cache can dedupe a repeated
|
|
342
|
+
* "Continue" click.
|
|
343
|
+
*
|
|
344
|
+
* The value actually sent as `Idempotency-Key` is produced by
|
|
345
|
+
* {@link newIdempotencyKey} / {@link attemptIdempotencyKey}.
|
|
346
|
+
*
|
|
347
|
+
* Exported for use by SDK hooks (e.g. payment link flows).
|
|
315
348
|
*/
|
|
316
349
|
declare function generateIdempotencyKey(params: Record<string, unknown>): string;
|
|
350
|
+
/**
|
|
351
|
+
* Generates a fresh, globally unique `Idempotency-Key` (RFC 4122 v4 UUID).
|
|
352
|
+
* This is the only value that should ever be sent on the wire.
|
|
353
|
+
*/
|
|
354
|
+
declare function newIdempotencyKey(): string;
|
|
355
|
+
/**
|
|
356
|
+
* Resolves the stable per-checkout-attempt wire key for a deterministic
|
|
357
|
+
* lookup key (see {@link generateIdempotencyKey}).
|
|
358
|
+
*
|
|
359
|
+
* The first call for a lookup key mints a UUID and stores it in
|
|
360
|
+
* `sessionStorage` (falling back to a module-level map when storage is
|
|
361
|
+
* unavailable); every later call in the same tab returns that same UUID, so a
|
|
362
|
+
* repeated "Continue" click is still deduped by the backend. A different tab,
|
|
363
|
+
* a different shopper or a cleared store yields a different UUID.
|
|
364
|
+
*/
|
|
365
|
+
declare function attemptIdempotencyKey(lookupKey: string): string;
|
|
366
|
+
/**
|
|
367
|
+
* Forgets every stored per-attempt key, so the next checkout attempt gets a
|
|
368
|
+
* fresh `Idempotency-Key`. Call it after a completed checkout (and in tests).
|
|
369
|
+
*/
|
|
370
|
+
declare function clearIdempotencyAttemptKeys(): void;
|
|
317
371
|
/**
|
|
318
372
|
* Reevit API Client
|
|
319
373
|
*/
|
|
@@ -344,6 +398,13 @@ declare class ReevitAPIClient {
|
|
|
344
398
|
data?: PaymentDetailResponse;
|
|
345
399
|
error?: PaymentError;
|
|
346
400
|
}>;
|
|
401
|
+
/**
|
|
402
|
+
* Retrieves a server-created checkout session using its public session secret.
|
|
403
|
+
*/
|
|
404
|
+
getCheckoutSession(sessionSecret: string): Promise<{
|
|
405
|
+
data?: CheckoutSessionResponse;
|
|
406
|
+
error?: PaymentError;
|
|
407
|
+
}>;
|
|
347
408
|
/**
|
|
348
409
|
* Confirms a payment after PSP callback
|
|
349
410
|
*/
|
|
@@ -389,6 +450,19 @@ declare function createReevitClient(config: ReevitAPIClientConfig): ReevitAPICli
|
|
|
389
450
|
* Shared utilities for Reevit SDKs
|
|
390
451
|
*/
|
|
391
452
|
|
|
453
|
+
/**
|
|
454
|
+
* Returns how many decimal places a currency's minor unit uses: 2 for GHS and
|
|
455
|
+
* NGN, 0 for XOF, XAF, RWF, UGX, JPY and friends.
|
|
456
|
+
*
|
|
457
|
+
* Dividing every amount by 100 renders a 5,000 XOF charge as "XOF 50.00" while
|
|
458
|
+
* the shopper is actually charged 5,000 — which is why this exists.
|
|
459
|
+
*/
|
|
460
|
+
declare function currencyExponent(currency: string): number;
|
|
461
|
+
/**
|
|
462
|
+
* Converts a major-unit amount (what a shopper types) into the minor units the
|
|
463
|
+
* API expects: `toMinorUnits(45, 'GHS') === 4500`, `toMinorUnits(5000, 'XOF') === 5000`.
|
|
464
|
+
*/
|
|
465
|
+
declare function toMinorUnits(major: number, currency: string): number;
|
|
392
466
|
/**
|
|
393
467
|
* Formats an amount from smallest currency unit to display format
|
|
394
468
|
*/
|
|
@@ -424,6 +498,18 @@ declare function detectCountryFromCurrency(currency: string): string;
|
|
|
424
498
|
|
|
425
499
|
/**
|
|
426
500
|
* Intent identity + cache helpers
|
|
501
|
+
*
|
|
502
|
+
* Two different keys are in play here and mixing them up is a money bug:
|
|
503
|
+
*
|
|
504
|
+
* - the **lookup key** is the deterministic djb2 hash of the checkout
|
|
505
|
+
* parameters (`generateIdempotencyKey`). It identifies "the same checkout
|
|
506
|
+
* attempt" for the in-flight cache and is never sent to the API.
|
|
507
|
+
* - the **wire key** is the per-attempt UUID (`attemptIdempotencyKey`) that
|
|
508
|
+
* goes out as the `Idempotency-Key` header.
|
|
509
|
+
*
|
|
510
|
+
* The cache is keyed by the lookup key and remembers the wire key it minted.
|
|
511
|
+
* The public helpers accept either key so callers that only ever saw the
|
|
512
|
+
* `idempotencyKey` field keep working unchanged.
|
|
427
513
|
*/
|
|
428
514
|
|
|
429
515
|
interface IntentIdentityOptions {
|
|
@@ -438,16 +524,21 @@ interface IntentCacheEntry {
|
|
|
438
524
|
response?: PaymentIntentResponse;
|
|
439
525
|
expiresAt: number;
|
|
440
526
|
reference?: string;
|
|
527
|
+
/** The `Idempotency-Key` sent on the wire for this attempt. */
|
|
528
|
+
idempotencyKey?: string;
|
|
441
529
|
}
|
|
442
530
|
declare function resolveIntentIdentity(options: IntentIdentityOptions): {
|
|
531
|
+
/** The value to send as `Idempotency-Key`. */
|
|
443
532
|
idempotencyKey: string;
|
|
533
|
+
/** The local cache key. Never send this on the wire. */
|
|
534
|
+
lookupKey: string;
|
|
444
535
|
reference: string;
|
|
445
536
|
cacheEntry?: IntentCacheEntry;
|
|
446
537
|
};
|
|
447
|
-
declare function getIntentCacheEntry(
|
|
448
|
-
declare function cacheIntentPromise(
|
|
449
|
-
declare function cacheIntentResponse(
|
|
450
|
-
declare function clearIntentCacheEntry(
|
|
538
|
+
declare function getIntentCacheEntry(key: string): IntentCacheEntry | undefined;
|
|
539
|
+
declare function cacheIntentPromise(key: string, promise: Promise<PaymentIntentResponse>): IntentCacheEntry;
|
|
540
|
+
declare function cacheIntentResponse(key: string, response: PaymentIntentResponse): IntentCacheEntry;
|
|
541
|
+
declare function clearIntentCacheEntry(key: string): void;
|
|
451
542
|
|
|
452
543
|
/**
|
|
453
544
|
* Reevit State Machine
|
|
@@ -494,4 +585,4 @@ declare function createInitialState(): ReevitState;
|
|
|
494
585
|
*/
|
|
495
586
|
declare function reevitReducer(state: ReevitState, action: ReevitAction): ReevitState;
|
|
496
587
|
|
|
497
|
-
export { type APIErrorResponse, type CardFormData, type CheckoutProviderOption, type CheckoutState, type ConfirmPaymentRequest, type CreatePaymentIntentRequest, type HubtelSessionResponse, type IntentCacheEntry, 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, cacheIntentPromise, cacheIntentResponse, clearIntentCacheEntry, cn, createInitialState, createReevitClient, createThemeVariables, detectCountryFromCurrency, detectNetwork, formatAmount, formatPhone, generateIdempotencyKey, generateReference, getIntentCacheEntry, reevitReducer, resolveIntentIdentity, validatePhone };
|
|
588
|
+
export { type APIErrorResponse, type CardFormData, type CheckoutProviderOption, type CheckoutSessionResponse, type CheckoutState, type ConfirmPaymentRequest, type CreatePaymentIntentRequest, type HubtelSessionResponse, type IntentCacheEntry, 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 ReevitAPIResult, type ReevitAction, type ReevitCheckoutCallbacks, type ReevitCheckoutConfig, type ReevitState, type ReevitTheme, attemptIdempotencyKey, cacheIntentPromise, cacheIntentResponse, clearIdempotencyAttemptKeys, clearIntentCacheEntry, cn, createInitialState, createPaymentError, createReevitClient, createThemeVariables, currencyExponent, detectCountryFromCurrency, detectNetwork, formatAmount, formatPhone, generateIdempotencyKey, generateReference, getIntentCacheEntry, isPaymentError, newIdempotencyKey, reevitReducer, resolveIntentIdentity, toMinorUnits, validatePhone };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,10 +10,12 @@ type PaymentSource = 'payment_link' | 'api' | 'subscription';
|
|
|
10
10
|
interface ReevitCheckoutConfig {
|
|
11
11
|
/** Your Reevit public key (required for API-created intents; omit for payment links) */
|
|
12
12
|
publicKey?: string;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
|
|
13
|
+
/** Server-created checkout session secret. Prefer this for browser checkouts. */
|
|
14
|
+
sessionSecret?: string;
|
|
15
|
+
/** Amount in the smallest currency unit (e.g., pesewas for GHS). Required unless sessionSecret or initialPaymentIntent is provided. */
|
|
16
|
+
amount?: number;
|
|
17
|
+
/** Currency code (e.g., 'GHS', 'NGN', 'USD'). Required unless sessionSecret or initialPaymentIntent is provided. */
|
|
18
|
+
currency?: string;
|
|
17
19
|
/** Customer email address */
|
|
18
20
|
email?: string;
|
|
19
21
|
/** Customer phone number (required for mobile money) */
|
|
@@ -247,6 +249,7 @@ interface PaymentIntentResponse {
|
|
|
247
249
|
provider_ref_id?: string;
|
|
248
250
|
status: string;
|
|
249
251
|
client_secret: string;
|
|
252
|
+
session_secret?: string;
|
|
250
253
|
psp_public_key: string;
|
|
251
254
|
psp_credentials?: {
|
|
252
255
|
merchantAccount?: string | number;
|
|
@@ -267,6 +270,13 @@ interface PaymentIntentResponse {
|
|
|
267
270
|
}>;
|
|
268
271
|
branding?: Record<string, unknown>;
|
|
269
272
|
}
|
|
273
|
+
interface CheckoutSessionResponse {
|
|
274
|
+
id: string;
|
|
275
|
+
client_secret: string;
|
|
276
|
+
session_secret: string;
|
|
277
|
+
payment_intent: PaymentIntentResponse;
|
|
278
|
+
expires_at?: string;
|
|
279
|
+
}
|
|
270
280
|
interface ConfirmPaymentRequest {
|
|
271
281
|
provider_ref_id: string;
|
|
272
282
|
provider_data?: Record<string, unknown>;
|
|
@@ -298,8 +308,15 @@ interface PaymentDetailResponse {
|
|
|
298
308
|
interface APIErrorResponse {
|
|
299
309
|
code: string;
|
|
300
310
|
message: string;
|
|
301
|
-
details?: Record<string,
|
|
311
|
+
details?: Record<string, unknown>;
|
|
302
312
|
}
|
|
313
|
+
type ReevitAPIResult<T> = {
|
|
314
|
+
data: T;
|
|
315
|
+
error?: never;
|
|
316
|
+
} | {
|
|
317
|
+
data?: never;
|
|
318
|
+
error: PaymentError;
|
|
319
|
+
};
|
|
303
320
|
interface ReevitAPIClientConfig {
|
|
304
321
|
/** Your Reevit public key */
|
|
305
322
|
publicKey?: string;
|
|
@@ -309,11 +326,48 @@ interface ReevitAPIClientConfig {
|
|
|
309
326
|
timeout?: number;
|
|
310
327
|
}
|
|
311
328
|
/**
|
|
312
|
-
*
|
|
313
|
-
|
|
314
|
-
|
|
329
|
+
* Creates a PaymentError from an API error response
|
|
330
|
+
*/
|
|
331
|
+
declare function createPaymentError(response: Response, errorData: APIErrorResponse): PaymentError;
|
|
332
|
+
declare function isPaymentError(error: unknown): error is PaymentError;
|
|
333
|
+
/**
|
|
334
|
+
* Generates a deterministic **cache/lookup** key from input parameters.
|
|
335
|
+
*
|
|
336
|
+
* NEVER SEND THIS ON THE WIRE. It is a 32-bit djb2 hash bucketed into
|
|
337
|
+
* 5-minute windows, so two unrelated shoppers can collide and be handed each
|
|
338
|
+
* other's payment intent (and therefore each other's `client_secret`), and a
|
|
339
|
+
* shopper legitimately buying the same item twice inside one window would be
|
|
340
|
+
* charged once. Its only job is to identify "the same checkout attempt" inside
|
|
341
|
+
* a single browser tab so the in-flight intent cache can dedupe a repeated
|
|
342
|
+
* "Continue" click.
|
|
343
|
+
*
|
|
344
|
+
* The value actually sent as `Idempotency-Key` is produced by
|
|
345
|
+
* {@link newIdempotencyKey} / {@link attemptIdempotencyKey}.
|
|
346
|
+
*
|
|
347
|
+
* Exported for use by SDK hooks (e.g. payment link flows).
|
|
315
348
|
*/
|
|
316
349
|
declare function generateIdempotencyKey(params: Record<string, unknown>): string;
|
|
350
|
+
/**
|
|
351
|
+
* Generates a fresh, globally unique `Idempotency-Key` (RFC 4122 v4 UUID).
|
|
352
|
+
* This is the only value that should ever be sent on the wire.
|
|
353
|
+
*/
|
|
354
|
+
declare function newIdempotencyKey(): string;
|
|
355
|
+
/**
|
|
356
|
+
* Resolves the stable per-checkout-attempt wire key for a deterministic
|
|
357
|
+
* lookup key (see {@link generateIdempotencyKey}).
|
|
358
|
+
*
|
|
359
|
+
* The first call for a lookup key mints a UUID and stores it in
|
|
360
|
+
* `sessionStorage` (falling back to a module-level map when storage is
|
|
361
|
+
* unavailable); every later call in the same tab returns that same UUID, so a
|
|
362
|
+
* repeated "Continue" click is still deduped by the backend. A different tab,
|
|
363
|
+
* a different shopper or a cleared store yields a different UUID.
|
|
364
|
+
*/
|
|
365
|
+
declare function attemptIdempotencyKey(lookupKey: string): string;
|
|
366
|
+
/**
|
|
367
|
+
* Forgets every stored per-attempt key, so the next checkout attempt gets a
|
|
368
|
+
* fresh `Idempotency-Key`. Call it after a completed checkout (and in tests).
|
|
369
|
+
*/
|
|
370
|
+
declare function clearIdempotencyAttemptKeys(): void;
|
|
317
371
|
/**
|
|
318
372
|
* Reevit API Client
|
|
319
373
|
*/
|
|
@@ -344,6 +398,13 @@ declare class ReevitAPIClient {
|
|
|
344
398
|
data?: PaymentDetailResponse;
|
|
345
399
|
error?: PaymentError;
|
|
346
400
|
}>;
|
|
401
|
+
/**
|
|
402
|
+
* Retrieves a server-created checkout session using its public session secret.
|
|
403
|
+
*/
|
|
404
|
+
getCheckoutSession(sessionSecret: string): Promise<{
|
|
405
|
+
data?: CheckoutSessionResponse;
|
|
406
|
+
error?: PaymentError;
|
|
407
|
+
}>;
|
|
347
408
|
/**
|
|
348
409
|
* Confirms a payment after PSP callback
|
|
349
410
|
*/
|
|
@@ -389,6 +450,19 @@ declare function createReevitClient(config: ReevitAPIClientConfig): ReevitAPICli
|
|
|
389
450
|
* Shared utilities for Reevit SDKs
|
|
390
451
|
*/
|
|
391
452
|
|
|
453
|
+
/**
|
|
454
|
+
* Returns how many decimal places a currency's minor unit uses: 2 for GHS and
|
|
455
|
+
* NGN, 0 for XOF, XAF, RWF, UGX, JPY and friends.
|
|
456
|
+
*
|
|
457
|
+
* Dividing every amount by 100 renders a 5,000 XOF charge as "XOF 50.00" while
|
|
458
|
+
* the shopper is actually charged 5,000 — which is why this exists.
|
|
459
|
+
*/
|
|
460
|
+
declare function currencyExponent(currency: string): number;
|
|
461
|
+
/**
|
|
462
|
+
* Converts a major-unit amount (what a shopper types) into the minor units the
|
|
463
|
+
* API expects: `toMinorUnits(45, 'GHS') === 4500`, `toMinorUnits(5000, 'XOF') === 5000`.
|
|
464
|
+
*/
|
|
465
|
+
declare function toMinorUnits(major: number, currency: string): number;
|
|
392
466
|
/**
|
|
393
467
|
* Formats an amount from smallest currency unit to display format
|
|
394
468
|
*/
|
|
@@ -424,6 +498,18 @@ declare function detectCountryFromCurrency(currency: string): string;
|
|
|
424
498
|
|
|
425
499
|
/**
|
|
426
500
|
* Intent identity + cache helpers
|
|
501
|
+
*
|
|
502
|
+
* Two different keys are in play here and mixing them up is a money bug:
|
|
503
|
+
*
|
|
504
|
+
* - the **lookup key** is the deterministic djb2 hash of the checkout
|
|
505
|
+
* parameters (`generateIdempotencyKey`). It identifies "the same checkout
|
|
506
|
+
* attempt" for the in-flight cache and is never sent to the API.
|
|
507
|
+
* - the **wire key** is the per-attempt UUID (`attemptIdempotencyKey`) that
|
|
508
|
+
* goes out as the `Idempotency-Key` header.
|
|
509
|
+
*
|
|
510
|
+
* The cache is keyed by the lookup key and remembers the wire key it minted.
|
|
511
|
+
* The public helpers accept either key so callers that only ever saw the
|
|
512
|
+
* `idempotencyKey` field keep working unchanged.
|
|
427
513
|
*/
|
|
428
514
|
|
|
429
515
|
interface IntentIdentityOptions {
|
|
@@ -438,16 +524,21 @@ interface IntentCacheEntry {
|
|
|
438
524
|
response?: PaymentIntentResponse;
|
|
439
525
|
expiresAt: number;
|
|
440
526
|
reference?: string;
|
|
527
|
+
/** The `Idempotency-Key` sent on the wire for this attempt. */
|
|
528
|
+
idempotencyKey?: string;
|
|
441
529
|
}
|
|
442
530
|
declare function resolveIntentIdentity(options: IntentIdentityOptions): {
|
|
531
|
+
/** The value to send as `Idempotency-Key`. */
|
|
443
532
|
idempotencyKey: string;
|
|
533
|
+
/** The local cache key. Never send this on the wire. */
|
|
534
|
+
lookupKey: string;
|
|
444
535
|
reference: string;
|
|
445
536
|
cacheEntry?: IntentCacheEntry;
|
|
446
537
|
};
|
|
447
|
-
declare function getIntentCacheEntry(
|
|
448
|
-
declare function cacheIntentPromise(
|
|
449
|
-
declare function cacheIntentResponse(
|
|
450
|
-
declare function clearIntentCacheEntry(
|
|
538
|
+
declare function getIntentCacheEntry(key: string): IntentCacheEntry | undefined;
|
|
539
|
+
declare function cacheIntentPromise(key: string, promise: Promise<PaymentIntentResponse>): IntentCacheEntry;
|
|
540
|
+
declare function cacheIntentResponse(key: string, response: PaymentIntentResponse): IntentCacheEntry;
|
|
541
|
+
declare function clearIntentCacheEntry(key: string): void;
|
|
451
542
|
|
|
452
543
|
/**
|
|
453
544
|
* Reevit State Machine
|
|
@@ -494,4 +585,4 @@ declare function createInitialState(): ReevitState;
|
|
|
494
585
|
*/
|
|
495
586
|
declare function reevitReducer(state: ReevitState, action: ReevitAction): ReevitState;
|
|
496
587
|
|
|
497
|
-
export { type APIErrorResponse, type CardFormData, type CheckoutProviderOption, type CheckoutState, type ConfirmPaymentRequest, type CreatePaymentIntentRequest, type HubtelSessionResponse, type IntentCacheEntry, 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, cacheIntentPromise, cacheIntentResponse, clearIntentCacheEntry, cn, createInitialState, createReevitClient, createThemeVariables, detectCountryFromCurrency, detectNetwork, formatAmount, formatPhone, generateIdempotencyKey, generateReference, getIntentCacheEntry, reevitReducer, resolveIntentIdentity, validatePhone };
|
|
588
|
+
export { type APIErrorResponse, type CardFormData, type CheckoutProviderOption, type CheckoutSessionResponse, type CheckoutState, type ConfirmPaymentRequest, type CreatePaymentIntentRequest, type HubtelSessionResponse, type IntentCacheEntry, 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 ReevitAPIResult, type ReevitAction, type ReevitCheckoutCallbacks, type ReevitCheckoutConfig, type ReevitState, type ReevitTheme, attemptIdempotencyKey, cacheIntentPromise, cacheIntentResponse, clearIdempotencyAttemptKeys, clearIntentCacheEntry, cn, createInitialState, createPaymentError, createReevitClient, createThemeVariables, currencyExponent, detectCountryFromCurrency, detectNetwork, formatAmount, formatPhone, generateIdempotencyKey, generateReference, getIntentCacheEntry, isPaymentError, newIdempotencyKey, reevitReducer, resolveIntentIdentity, toMinorUnits, validatePhone };
|