payluk-escrow-inline-checkout 0.2.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -4
- package/dist/chunk-E6SFCKQP.js +183 -0
- package/dist/chunk-E6SFCKQP.js.map +1 -0
- package/dist/index.cjs +116 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +1 -1
- package/dist/react/index.cjs +138 -38
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +4 -1
- package/dist/react/index.d.ts +4 -1
- package/dist/react/index.js +28 -19
- package/dist/react/index.js.map +1 -1
- package/package.json +6 -4
- package/dist/chunk-RMEQANVK.js +0 -90
- package/dist/chunk-RMEQANVK.js.map +0 -1
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ A lightweight client SDK that initializes your checkout configuration, creates a
|
|
|
6
6
|
- Promise-based API.
|
|
7
7
|
- First-class TypeScript types.
|
|
8
8
|
- Optional React hook with loading/error state.
|
|
9
|
+
- Multi-escrow support: pay for multiple escrows in a single checkout session.
|
|
9
10
|
|
|
10
11
|
## Installation
|
|
11
12
|
|
|
@@ -178,6 +179,106 @@ export function CheckoutButton() {
|
|
|
178
179
|
- Only use publishable keys in the browser. Keep any secret keys on your server.
|
|
179
180
|
- Validate inputs on your backend and return the required session payload.
|
|
180
181
|
|
|
182
|
+
## Multi-Escrow Payments
|
|
183
|
+
|
|
184
|
+
You can now pay for multiple escrows in a single checkout session by passing an array of payment tokens. This is useful when a customer wants to pay for multiple items or services at once.
|
|
185
|
+
|
|
186
|
+
### Requirements
|
|
187
|
+
|
|
188
|
+
- All escrows must belong to the same merchant
|
|
189
|
+
- All escrows must be in `PENDING` status
|
|
190
|
+
- If using merchant escrows, a `customerId` is required
|
|
191
|
+
- The total amount from all escrows will be charged in a single transaction
|
|
192
|
+
|
|
193
|
+
### Vanilla JS/TS Example
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { initEscrowCheckout, pay } from 'payluk-escrow-inline-checkout';
|
|
197
|
+
|
|
198
|
+
// Initialize once
|
|
199
|
+
initEscrowCheckout({
|
|
200
|
+
publicKey: '<YOUR_PUBLISHABLE_KEY>'
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Pay for multiple escrows
|
|
204
|
+
async function onPayMultipleClick() {
|
|
205
|
+
try {
|
|
206
|
+
await pay({
|
|
207
|
+
// Pass an array of payment tokens
|
|
208
|
+
paymentToken: ['TOKEN_1', 'TOKEN_2', 'TOKEN_3'],
|
|
209
|
+
reference: '<REFERENCE_ID>',
|
|
210
|
+
redirectUrl: 'https://your-app.example.com/checkout/complete',
|
|
211
|
+
logoUrl: 'https://mediacloud.me/media/W8HU9TK245QF528ZULCFSJXX2SBBLT.jpg',
|
|
212
|
+
brand: 'YourBrand',
|
|
213
|
+
customerId: 'customer_123', // Required for merchant escrows
|
|
214
|
+
callback: (result) => {
|
|
215
|
+
// result.paymentId will contain comma-separated IDs: "id1,id2,id3"
|
|
216
|
+
// result.reference will contain unique refs: "REF_1,REF_2,REF_3"
|
|
217
|
+
console.log('Multi-escrow checkout result:', result);
|
|
218
|
+
},
|
|
219
|
+
onClose: () => {
|
|
220
|
+
console.log('Widget closed');
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
} catch (err) {
|
|
224
|
+
console.error('Payment failed:', err);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### React Example
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
import React from 'react';
|
|
233
|
+
import { useEscrowCheckout } from 'payluk-escrow-inline-checkout/react';
|
|
234
|
+
|
|
235
|
+
export function MultiEscrowCheckoutButton() {
|
|
236
|
+
const { pay } = useEscrowCheckout();
|
|
237
|
+
|
|
238
|
+
const handleClick = async () => {
|
|
239
|
+
try {
|
|
240
|
+
await pay({
|
|
241
|
+
// Array of payment tokens for multi-escrow checkout
|
|
242
|
+
paymentToken: ['TOKEN_1', 'TOKEN_2', 'TOKEN_3'],
|
|
243
|
+
reference: '<REFERENCE_ID>',
|
|
244
|
+
redirectUrl: 'https://your-app.example.com/checkout/complete',
|
|
245
|
+
customerId: 'customer_123',
|
|
246
|
+
callback: (result) => {
|
|
247
|
+
console.log('Payment successful:', result);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
} catch (err) {
|
|
251
|
+
console.error('Payment failed:', err);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<button onClick={handleClick}>
|
|
257
|
+
Pay for Multiple Items
|
|
258
|
+
</button>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### How It Works
|
|
264
|
+
|
|
265
|
+
1. **Individual Processing**: Each escrow is processed individually with its own payment intent
|
|
266
|
+
2. **Aggregated Total**: The checkout widget displays the total amount from all escrows
|
|
267
|
+
3. **Unique References**: Multi-payments generate unique references for each escrow (e.g., `REF_1`, `REF_2`, `REF_3`)
|
|
268
|
+
4. **Single Transaction**: The customer completes one payment for all escrows combined
|
|
269
|
+
5. **Backward Compatible**: Single payment tokens (strings) still work exactly as before
|
|
270
|
+
|
|
271
|
+
### Response Format
|
|
272
|
+
|
|
273
|
+
When using multi-escrow payments, the callback receives:
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
{
|
|
277
|
+
paymentId: "token1,token2,token3", // Comma-separated escrow IDs
|
|
278
|
+
reference: "REF_1,REF_2,REF_3" // Comma-separated unique references
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
181
282
|
## API
|
|
182
283
|
|
|
183
284
|
### `initEscrowCheckout(config)`
|
|
@@ -207,20 +308,27 @@ initEscrowCheckout({
|
|
|
207
308
|
Creates a checkout session via your backend and opens the widget.
|
|
208
309
|
|
|
209
310
|
**Required:**
|
|
210
|
-
- `paymentToken`: `string`
|
|
311
|
+
- `paymentToken`: `string | string[]` — single payment token or array of tokens for multi-escrow checkout
|
|
211
312
|
- `reference`: `string`
|
|
212
313
|
- `redirectUrl`: `string`
|
|
213
314
|
|
|
214
315
|
**Optional:**
|
|
215
316
|
- `logoUrl?`: `string`
|
|
216
|
-
- `customerId?`: `string` —
|
|
317
|
+
- `customerId?`: `string` — required for merchant escrows, optional for business escrows
|
|
217
318
|
- `brand?`: `string`
|
|
218
319
|
- `callback?`: `(result: unknown) => void`
|
|
219
320
|
- `onClose?`: `() => void`
|
|
321
|
+
- `extra?`: `Record<string, unknown>` — additional widget configuration
|
|
220
322
|
|
|
221
323
|
**Returns:**
|
|
222
324
|
- `Promise<void>` that resolves when the widget is opened (and rejects on errors).
|
|
223
325
|
|
|
326
|
+
**Multi-Escrow Notes:**
|
|
327
|
+
- When using an array of payment tokens, all escrows must belong to the same merchant
|
|
328
|
+
- The checkout widget will display the aggregated total amount
|
|
329
|
+
- Each escrow is processed individually with its own payment intent
|
|
330
|
+
- Empty arrays or arrays containing empty strings will be rejected
|
|
331
|
+
|
|
224
332
|
### `useEscrowCheckout(): { ready, loading, error, pay }`
|
|
225
333
|
|
|
226
334
|
React hook that exposes:
|
|
@@ -246,7 +354,22 @@ import { useEscrowCheckout } from 'payluk-escrow-inline-checkout/react';
|
|
|
246
354
|
Common issues:
|
|
247
355
|
- **Not initialized:** Ensure `initEscrowCheckout({ publicKey })` is called before `pay(...)`.
|
|
248
356
|
- **Browser-only:** Do not call `pay(...)` on the server.
|
|
249
|
-
- **Network/API errors:** If the session endpoint fails, `pay(...)` will reject with
|
|
357
|
+
- **Network/API errors:** If the session endpoint fails, `pay(...)` will reject with an `EscrowCheckoutError` that includes a `code`, optional HTTP `status`, and `details`.
|
|
358
|
+
|
|
359
|
+
`EscrowCheckoutError` codes:
|
|
360
|
+
- `NOT_INITIALIZED`
|
|
361
|
+
- `BROWSER_ONLY`
|
|
362
|
+
- `INVALID_INPUT`
|
|
363
|
+
- `WIDGET_LOAD`
|
|
364
|
+
- `NETWORK`
|
|
365
|
+
- `SESSION_CREATE`
|
|
366
|
+
- `SESSION_RESPONSE`
|
|
367
|
+
|
|
368
|
+
You can import the error class if you want stricter checks in TypeScript:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
import { EscrowCheckoutError } from 'payluk-escrow-inline-checkout';
|
|
372
|
+
```
|
|
250
373
|
|
|
251
374
|
**Example:**
|
|
252
375
|
|
|
@@ -254,7 +377,12 @@ Common issues:
|
|
|
254
377
|
try {
|
|
255
378
|
await pay({ /* ... */ });
|
|
256
379
|
} catch (err) {
|
|
257
|
-
|
|
380
|
+
if (err instanceof EscrowCheckoutError) {
|
|
381
|
+
console.error(err.code, err.status, err.message);
|
|
382
|
+
alert(err.message);
|
|
383
|
+
} else {
|
|
384
|
+
alert('Checkout failed');
|
|
385
|
+
}
|
|
258
386
|
}
|
|
259
387
|
```
|
|
260
388
|
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var EscrowCheckoutError = class extends Error {
|
|
3
|
+
constructor(message, code, options) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "EscrowCheckoutError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.status = options?.status;
|
|
8
|
+
this.details = options?.details;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var DEFAULT_SCRIPT_URL = "https://checkout.payluk.ng/escrow-checkout.min.js";
|
|
12
|
+
var DEFAULT_GLOBAL_NAME = "EscrowCheckout";
|
|
13
|
+
var CONFIG = null;
|
|
14
|
+
function initEscrowCheckout(config) {
|
|
15
|
+
if (!isNonEmptyString(config?.publicKey)) {
|
|
16
|
+
throw new EscrowCheckoutError('initEscrowCheckout(...) requires "publicKey".', "INVALID_INPUT");
|
|
17
|
+
}
|
|
18
|
+
CONFIG = {
|
|
19
|
+
publicKey: config.publicKey.trim(),
|
|
20
|
+
scriptUrl: config.scriptUrlOverride || DEFAULT_SCRIPT_URL,
|
|
21
|
+
globalName: config.globalName || DEFAULT_GLOBAL_NAME,
|
|
22
|
+
crossOrigin: config.crossOrigin ?? "anonymous"
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function assertConfigured(config) {
|
|
26
|
+
if (!config) {
|
|
27
|
+
throw new EscrowCheckoutError(
|
|
28
|
+
"Escrow checkout not initialized. Call initEscrowCheckout({ publicKey }) first.",
|
|
29
|
+
"NOT_INITIALIZED"
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function normalizeError(error) {
|
|
34
|
+
if (error instanceof Error) return error;
|
|
35
|
+
if (typeof error === "string") return new Error(error);
|
|
36
|
+
return new Error("Unknown error");
|
|
37
|
+
}
|
|
38
|
+
function isNonEmptyString(value) {
|
|
39
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
40
|
+
}
|
|
41
|
+
function isValidPaymentToken(value) {
|
|
42
|
+
if (isNonEmptyString(value)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
return value.length > 0 && value.every((token) => isNonEmptyString(token));
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
function isSessionResponse(value) {
|
|
51
|
+
if (!value || typeof value !== "object") return false;
|
|
52
|
+
return "session" in value;
|
|
53
|
+
}
|
|
54
|
+
async function parseErrorBody(resp) {
|
|
55
|
+
const text = await resp.text();
|
|
56
|
+
if (!text) return {};
|
|
57
|
+
try {
|
|
58
|
+
const json = JSON.parse(text);
|
|
59
|
+
const message = typeof json?.message === "string" ? json.message : void 0;
|
|
60
|
+
return { message, details: json };
|
|
61
|
+
} catch {
|
|
62
|
+
return { message: text, details: text };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function parseJsonResponse(resp) {
|
|
66
|
+
try {
|
|
67
|
+
return await resp.json();
|
|
68
|
+
} catch {
|
|
69
|
+
throw new EscrowCheckoutError("Invalid JSON response from session endpoint.", "SESSION_RESPONSE", {
|
|
70
|
+
status: resp.status
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function loadWidget() {
|
|
75
|
+
assertConfigured(CONFIG);
|
|
76
|
+
const { scriptUrl, globalName, crossOrigin } = CONFIG;
|
|
77
|
+
if (typeof window === "undefined") {
|
|
78
|
+
throw new EscrowCheckoutError("EscrowCheckout can only be loaded in the browser.", "BROWSER_ONLY");
|
|
79
|
+
}
|
|
80
|
+
const win = window;
|
|
81
|
+
win.__escrowCheckoutLoader ?? (win.__escrowCheckoutLoader = {});
|
|
82
|
+
if (win.__escrowCheckoutLoader[scriptUrl]) {
|
|
83
|
+
return win.__escrowCheckoutLoader[scriptUrl];
|
|
84
|
+
}
|
|
85
|
+
if (typeof win[globalName] === "function") {
|
|
86
|
+
const fn = win[globalName];
|
|
87
|
+
win.__escrowCheckoutLoader[scriptUrl] = Promise.resolve(fn);
|
|
88
|
+
return fn;
|
|
89
|
+
}
|
|
90
|
+
win.__escrowCheckoutLoader[scriptUrl] = new Promise((resolve, reject) => {
|
|
91
|
+
const script = document.createElement("script");
|
|
92
|
+
script.src = scriptUrl;
|
|
93
|
+
script.async = true;
|
|
94
|
+
if (crossOrigin) script.crossOrigin = crossOrigin;
|
|
95
|
+
script.onload = () => {
|
|
96
|
+
const fn = win[globalName];
|
|
97
|
+
if (typeof fn === "function") resolve(fn);
|
|
98
|
+
else {
|
|
99
|
+
reject(
|
|
100
|
+
new EscrowCheckoutError(
|
|
101
|
+
`Escrow checkout script loaded, but window.${globalName} is not a function.`,
|
|
102
|
+
"WIDGET_LOAD"
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
script.onerror = () => reject(new EscrowCheckoutError(`Failed to load escrow checkout script: ${scriptUrl}`, "WIDGET_LOAD"));
|
|
108
|
+
document.head.appendChild(script);
|
|
109
|
+
});
|
|
110
|
+
return win.__escrowCheckoutLoader[scriptUrl];
|
|
111
|
+
}
|
|
112
|
+
async function createSession(input) {
|
|
113
|
+
assertConfigured(CONFIG);
|
|
114
|
+
const { publicKey } = CONFIG;
|
|
115
|
+
const apiBaseUrl = publicKey.startsWith("pk_live_") ? "https://live.payluk.ng" : "https://staging.live.payluk.ng";
|
|
116
|
+
let resp;
|
|
117
|
+
try {
|
|
118
|
+
resp = await fetch(`${apiBaseUrl}/v1/checkout/session`, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: { "Content-Type": "application/json" },
|
|
121
|
+
body: JSON.stringify({
|
|
122
|
+
paymentToken: input.paymentToken,
|
|
123
|
+
redirectUrl: input.redirectUrl,
|
|
124
|
+
reference: input.reference,
|
|
125
|
+
customerId: input.customerId,
|
|
126
|
+
publicKey
|
|
127
|
+
})
|
|
128
|
+
});
|
|
129
|
+
} catch (error) {
|
|
130
|
+
const normalized = normalizeError(error);
|
|
131
|
+
throw new EscrowCheckoutError("Network error while creating checkout session.", "NETWORK", {
|
|
132
|
+
details: normalized.message
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (!resp.ok) {
|
|
136
|
+
const { message, details } = await parseErrorBody(resp);
|
|
137
|
+
throw new EscrowCheckoutError(message || `Failed to create session (HTTP ${resp.status}).`, "SESSION_CREATE", {
|
|
138
|
+
status: resp.status,
|
|
139
|
+
details
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const data = await parseJsonResponse(resp);
|
|
143
|
+
if (!isSessionResponse(data)) {
|
|
144
|
+
throw new EscrowCheckoutError('Session response missing "session".', "SESSION_RESPONSE", {
|
|
145
|
+
status: resp.status,
|
|
146
|
+
details: data
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return data;
|
|
150
|
+
}
|
|
151
|
+
async function pay(input) {
|
|
152
|
+
if (typeof window === "undefined") {
|
|
153
|
+
throw new EscrowCheckoutError("pay(...) can only run in the browser.", "BROWSER_ONLY");
|
|
154
|
+
}
|
|
155
|
+
assertConfigured(CONFIG);
|
|
156
|
+
if (!input || !isValidPaymentToken(input.paymentToken)) {
|
|
157
|
+
throw new EscrowCheckoutError(
|
|
158
|
+
'pay(...) requires "paymentToken" (must be a non-empty string or array of non-empty strings).',
|
|
159
|
+
"INVALID_INPUT"
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (!isNonEmptyString(input.reference)) {
|
|
163
|
+
throw new EscrowCheckoutError('pay(...) requires "reference".', "INVALID_INPUT");
|
|
164
|
+
}
|
|
165
|
+
if (!isNonEmptyString(input.redirectUrl)) {
|
|
166
|
+
throw new EscrowCheckoutError('pay(...) requires "redirectUrl".', "INVALID_INPUT");
|
|
167
|
+
}
|
|
168
|
+
const [{ session }, widget] = await Promise.all([createSession(input), loadWidget()]);
|
|
169
|
+
widget({
|
|
170
|
+
session,
|
|
171
|
+
logoUrl: input.logoUrl,
|
|
172
|
+
brand: input.brand,
|
|
173
|
+
callback: input.callback,
|
|
174
|
+
onClose: input.onClose,
|
|
175
|
+
customerId: input.customerId,
|
|
176
|
+
publicKey: CONFIG.publicKey,
|
|
177
|
+
...input.extra ?? {}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export { EscrowCheckoutError, initEscrowCheckout, pay };
|
|
182
|
+
//# sourceMappingURL=chunk-E6SFCKQP.js.map
|
|
183
|
+
//# sourceMappingURL=chunk-E6SFCKQP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"chunk-E6SFCKQP.js","sourcesContent":["export type EscrowWidget = (options: Record<string, unknown>) => void;\n\nexport type EscrowCheckoutErrorCode =\n | 'NOT_INITIALIZED'\n | 'BROWSER_ONLY'\n | 'INVALID_INPUT'\n | 'WIDGET_LOAD'\n | 'NETWORK'\n | 'SESSION_CREATE'\n | 'SESSION_RESPONSE';\n\nexport class EscrowCheckoutError extends Error {\n code: EscrowCheckoutErrorCode;\n status?: number;\n details?: unknown;\n\n constructor(message: string, code: EscrowCheckoutErrorCode, options?: { status?: number; details?: unknown }) {\n super(message);\n this.name = 'EscrowCheckoutError';\n this.code = code;\n this.status = options?.status;\n this.details = options?.details;\n }\n}\n\nexport interface InitConfig {\n publicKey: string; // publishable key only\n /**\n * Optional overrides for experts. End users don't need to set these.\n */\n scriptUrlOverride?: string;\n globalName?: string; // default 'EscrowCheckout'\n crossOrigin?: '' | 'anonymous' | 'use-credentials';\n}\n\nexport interface PayInput {\n /**\n * Single escrow payment token or array of payment tokens for multi-escrow checkout.\n * When providing multiple tokens, they must all belong to the same merchant.\n */\n paymentToken: string | string[];\n reference: string;\n redirectUrl: string;\n logoUrl?: string;\n brand?: string;\n customerId?: string;\n /**\n * Extra fields supported by the widget.\n */\n extra?: Record<string, unknown>;\n callback?: (result: any) => void;\n onClose?: () => void;\n}\n\nexport interface SessionResponse {\n session: unknown;\n}\n\nconst DEFAULT_SCRIPT_URL = 'https://checkout.payluk.ng/escrow-checkout.min.js';\nconst DEFAULT_GLOBAL_NAME = 'EscrowCheckout';\n\ntype ResolvedConfig = Required<Pick<InitConfig, 'publicKey'>> & {\n scriptUrl: string;\n globalName: string;\n crossOrigin: '' | 'anonymous' | 'use-credentials';\n};\n\nlet CONFIG: ResolvedConfig | null = null;\n\n/**\n * Initialize the library once (e.g., in app bootstrap).\n * Hides script URL and session creation from consumers.\n */\nexport function initEscrowCheckout(config: InitConfig): void {\n if (!isNonEmptyString(config?.publicKey)) {\n throw new EscrowCheckoutError('initEscrowCheckout(...) requires \"publicKey\".', 'INVALID_INPUT');\n }\n CONFIG = {\n publicKey: config.publicKey.trim(),\n scriptUrl: config.scriptUrlOverride || DEFAULT_SCRIPT_URL,\n globalName: config.globalName || DEFAULT_GLOBAL_NAME,\n crossOrigin: config.crossOrigin ?? 'anonymous'\n };\n}\n\n/**\n * Narrow a provided config to ResolvedConfig or throw if not initialized.\n */\nfunction assertConfigured(config: ResolvedConfig | null): asserts config is ResolvedConfig {\n if (!config) {\n throw new EscrowCheckoutError(\n 'Escrow checkout not initialized. Call initEscrowCheckout({ publicKey }) first.',\n 'NOT_INITIALIZED'\n );\n }\n}\n\nfunction normalizeError(error: unknown): Error {\n if (error instanceof Error) return error;\n if (typeof error === 'string') return new Error(error);\n return new Error('Unknown error');\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nfunction isValidPaymentToken(value: unknown): value is string | string[] {\n // Check if it's a valid string\n if (isNonEmptyString(value)) {\n return true;\n }\n\n // Check if it's a valid array of strings\n if (Array.isArray(value)) {\n return value.length > 0 && value.every((token) => isNonEmptyString(token));\n }\n\n return false;\n}\n\nfunction isSessionResponse(value: unknown): value is SessionResponse {\n if (!value || typeof value !== 'object') return false;\n return 'session' in value;\n}\n\nasync function parseErrorBody(resp: Response): Promise<{ message?: string; details?: unknown }> {\n const text = await resp.text();\n if (!text) return {};\n\n try {\n const json = JSON.parse(text) as { message?: unknown };\n const message = typeof json?.message === 'string' ? json.message : undefined;\n return { message, details: json };\n } catch {\n return { message: text, details: text };\n }\n}\n\nasync function parseJsonResponse(resp: Response): Promise<unknown> {\n try {\n return await resp.json();\n } catch {\n throw new EscrowCheckoutError('Invalid JSON response from session endpoint.', 'SESSION_RESPONSE', {\n status: resp.status\n });\n }\n}\n\n/**\n * Internal: load the widget script once and return the global function.\n */\nasync function loadWidget(): Promise<EscrowWidget> {\n assertConfigured(CONFIG);\n const { scriptUrl, globalName, crossOrigin } = CONFIG;\n\n if (typeof window === 'undefined') {\n throw new EscrowCheckoutError('EscrowCheckout can only be loaded in the browser.', 'BROWSER_ONLY');\n }\n\n const win = window as any;\n win.__escrowCheckoutLoader ??= {};\n\n if (win.__escrowCheckoutLoader[scriptUrl]) {\n return win.__escrowCheckoutLoader[scriptUrl];\n }\n\n if (typeof win[globalName] === 'function') {\n const fn = win[globalName] as EscrowWidget;\n win.__escrowCheckoutLoader[scriptUrl] = Promise.resolve(fn);\n return fn;\n }\n\n win.__escrowCheckoutLoader[scriptUrl] = new Promise<EscrowWidget>((resolve, reject) => {\n const script = document.createElement('script');\n script.src = scriptUrl;\n script.async = true;\n if (crossOrigin) script.crossOrigin = crossOrigin;\n\n script.onload = () => {\n const fn = win[globalName];\n if (typeof fn === 'function') resolve(fn as EscrowWidget);\n else {\n reject(\n new EscrowCheckoutError(\n `Escrow checkout script loaded, but window.${globalName} is not a function.`,\n 'WIDGET_LOAD'\n )\n );\n }\n };\n\n script.onerror = () =>\n reject(new EscrowCheckoutError(`Failed to load escrow checkout script: ${scriptUrl}`, 'WIDGET_LOAD'));\n\n document.head.appendChild(script);\n });\n\n return win.__escrowCheckoutLoader[scriptUrl];\n}\n\n/**\n * Internal: create a checkout session by calling your API.\n * This is intentionally inside the lib (user doesn't implement it).\n */\nasync function createSession(input: PayInput): Promise<SessionResponse> {\n assertConfigured(CONFIG);\n const { publicKey } = CONFIG;\n const apiBaseUrl = publicKey.startsWith('pk_live_') ? 'https://live.payluk.ng' : 'https://staging.live.payluk.ng';\n let resp: Response;\n\n try {\n resp = await fetch(`${apiBaseUrl}/v1/checkout/session`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n paymentToken: input.paymentToken,\n redirectUrl: input.redirectUrl,\n reference: input.reference,\n customerId: input.customerId,\n publicKey\n })\n });\n } catch (error) {\n const normalized = normalizeError(error);\n throw new EscrowCheckoutError('Network error while creating checkout session.', 'NETWORK', {\n details: normalized.message\n });\n }\n\n if (!resp.ok) {\n const { message, details } = await parseErrorBody(resp);\n throw new EscrowCheckoutError(message || `Failed to create session (HTTP ${resp.status}).`, 'SESSION_CREATE', {\n status: resp.status,\n details\n });\n }\n\n const data = await parseJsonResponse(resp);\n if (!isSessionResponse(data)) {\n throw new EscrowCheckoutError('Session response missing \"session\".', 'SESSION_RESPONSE', {\n status: resp.status,\n details: data\n });\n }\n\n return data as SessionResponse;\n}\n\n/**\n * Public API: create the session and open the widget.\n * Consumers only supply business inputs (no script URL, no API calls).\n */\nexport async function pay(input: PayInput): Promise<void> {\n if (typeof window === 'undefined') {\n throw new EscrowCheckoutError('pay(...) can only run in the browser.', 'BROWSER_ONLY');\n }\n\n assertConfigured(CONFIG);\n if (!input || !isValidPaymentToken(input.paymentToken)) {\n throw new EscrowCheckoutError(\n 'pay(...) requires \"paymentToken\" (must be a non-empty string or array of non-empty strings).',\n 'INVALID_INPUT'\n );\n }\n if (!isNonEmptyString(input.reference)) {\n throw new EscrowCheckoutError('pay(...) requires \"reference\".', 'INVALID_INPUT');\n }\n if (!isNonEmptyString(input.redirectUrl)) {\n throw new EscrowCheckoutError('pay(...) requires \"redirectUrl\".', 'INVALID_INPUT');\n }\n\n const [{ session }, widget] = await Promise.all([createSession(input), loadWidget()]);\n\n widget({\n session,\n logoUrl: input.logoUrl,\n brand: input.brand,\n callback: input.callback,\n onClose: input.onClose,\n customerId: input.customerId,\n publicKey: CONFIG.publicKey,\n ...(input.extra ?? {})\n });\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
var EscrowCheckoutError = class extends Error {
|
|
5
|
+
constructor(message, code, options) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "EscrowCheckoutError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.status = options?.status;
|
|
10
|
+
this.details = options?.details;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
4
13
|
var DEFAULT_SCRIPT_URL = "https://checkout.payluk.ng/escrow-checkout.min.js";
|
|
5
14
|
var DEFAULT_GLOBAL_NAME = "EscrowCheckout";
|
|
6
15
|
var CONFIG = null;
|
|
7
16
|
function initEscrowCheckout(config) {
|
|
8
|
-
if (!config?.publicKey)
|
|
17
|
+
if (!isNonEmptyString(config?.publicKey)) {
|
|
18
|
+
throw new EscrowCheckoutError('initEscrowCheckout(...) requires "publicKey".', "INVALID_INPUT");
|
|
19
|
+
}
|
|
9
20
|
CONFIG = {
|
|
10
|
-
publicKey: config.publicKey,
|
|
21
|
+
publicKey: config.publicKey.trim(),
|
|
11
22
|
scriptUrl: config.scriptUrlOverride || DEFAULT_SCRIPT_URL,
|
|
12
23
|
globalName: config.globalName || DEFAULT_GLOBAL_NAME,
|
|
13
24
|
crossOrigin: config.crossOrigin ?? "anonymous"
|
|
@@ -15,14 +26,58 @@ function initEscrowCheckout(config) {
|
|
|
15
26
|
}
|
|
16
27
|
function assertConfigured(config) {
|
|
17
28
|
if (!config) {
|
|
18
|
-
throw new
|
|
29
|
+
throw new EscrowCheckoutError(
|
|
30
|
+
"Escrow checkout not initialized. Call initEscrowCheckout({ publicKey }) first.",
|
|
31
|
+
"NOT_INITIALIZED"
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function normalizeError(error) {
|
|
36
|
+
if (error instanceof Error) return error;
|
|
37
|
+
if (typeof error === "string") return new Error(error);
|
|
38
|
+
return new Error("Unknown error");
|
|
39
|
+
}
|
|
40
|
+
function isNonEmptyString(value) {
|
|
41
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
42
|
+
}
|
|
43
|
+
function isValidPaymentToken(value) {
|
|
44
|
+
if (isNonEmptyString(value)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
return value.length > 0 && value.every((token) => isNonEmptyString(token));
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
function isSessionResponse(value) {
|
|
53
|
+
if (!value || typeof value !== "object") return false;
|
|
54
|
+
return "session" in value;
|
|
55
|
+
}
|
|
56
|
+
async function parseErrorBody(resp) {
|
|
57
|
+
const text = await resp.text();
|
|
58
|
+
if (!text) return {};
|
|
59
|
+
try {
|
|
60
|
+
const json = JSON.parse(text);
|
|
61
|
+
const message = typeof json?.message === "string" ? json.message : void 0;
|
|
62
|
+
return { message, details: json };
|
|
63
|
+
} catch {
|
|
64
|
+
return { message: text, details: text };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function parseJsonResponse(resp) {
|
|
68
|
+
try {
|
|
69
|
+
return await resp.json();
|
|
70
|
+
} catch {
|
|
71
|
+
throw new EscrowCheckoutError("Invalid JSON response from session endpoint.", "SESSION_RESPONSE", {
|
|
72
|
+
status: resp.status
|
|
73
|
+
});
|
|
19
74
|
}
|
|
20
75
|
}
|
|
21
76
|
async function loadWidget() {
|
|
22
77
|
assertConfigured(CONFIG);
|
|
23
78
|
const { scriptUrl, globalName, crossOrigin } = CONFIG;
|
|
24
79
|
if (typeof window === "undefined") {
|
|
25
|
-
throw new
|
|
80
|
+
throw new EscrowCheckoutError("EscrowCheckout can only be loaded in the browser.", "BROWSER_ONLY");
|
|
26
81
|
}
|
|
27
82
|
const win = window;
|
|
28
83
|
win.__escrowCheckoutLoader ?? (win.__escrowCheckoutLoader = {});
|
|
@@ -42,9 +97,16 @@ async function loadWidget() {
|
|
|
42
97
|
script.onload = () => {
|
|
43
98
|
const fn = win[globalName];
|
|
44
99
|
if (typeof fn === "function") resolve(fn);
|
|
45
|
-
else
|
|
100
|
+
else {
|
|
101
|
+
reject(
|
|
102
|
+
new EscrowCheckoutError(
|
|
103
|
+
`Escrow checkout script loaded, but window.${globalName} is not a function.`,
|
|
104
|
+
"WIDGET_LOAD"
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
}
|
|
46
108
|
};
|
|
47
|
-
script.onerror = () => reject(new
|
|
109
|
+
script.onerror = () => reject(new EscrowCheckoutError(`Failed to load escrow checkout script: ${scriptUrl}`, "WIDGET_LOAD"));
|
|
48
110
|
document.head.appendChild(script);
|
|
49
111
|
});
|
|
50
112
|
return win.__escrowCheckoutLoader[scriptUrl];
|
|
@@ -53,26 +115,57 @@ async function createSession(input) {
|
|
|
53
115
|
assertConfigured(CONFIG);
|
|
54
116
|
const { publicKey } = CONFIG;
|
|
55
117
|
const apiBaseUrl = publicKey.startsWith("pk_live_") ? "https://live.payluk.ng" : "https://staging.live.payluk.ng";
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
118
|
+
let resp;
|
|
119
|
+
try {
|
|
120
|
+
resp = await fetch(`${apiBaseUrl}/v1/checkout/session`, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: { "Content-Type": "application/json" },
|
|
123
|
+
body: JSON.stringify({
|
|
124
|
+
paymentToken: input.paymentToken,
|
|
125
|
+
redirectUrl: input.redirectUrl,
|
|
126
|
+
reference: input.reference,
|
|
127
|
+
customerId: input.customerId,
|
|
128
|
+
publicKey
|
|
129
|
+
})
|
|
130
|
+
});
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const normalized = normalizeError(error);
|
|
133
|
+
throw new EscrowCheckoutError("Network error while creating checkout session.", "NETWORK", {
|
|
134
|
+
details: normalized.message
|
|
135
|
+
});
|
|
136
|
+
}
|
|
67
137
|
if (!resp.ok) {
|
|
68
|
-
const
|
|
69
|
-
throw new
|
|
138
|
+
const { message, details } = await parseErrorBody(resp);
|
|
139
|
+
throw new EscrowCheckoutError(message || `Failed to create session (HTTP ${resp.status}).`, "SESSION_CREATE", {
|
|
140
|
+
status: resp.status,
|
|
141
|
+
details
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const data = await parseJsonResponse(resp);
|
|
145
|
+
if (!isSessionResponse(data)) {
|
|
146
|
+
throw new EscrowCheckoutError('Session response missing "session".', "SESSION_RESPONSE", {
|
|
147
|
+
status: resp.status,
|
|
148
|
+
details: data
|
|
149
|
+
});
|
|
70
150
|
}
|
|
71
|
-
return
|
|
151
|
+
return data;
|
|
72
152
|
}
|
|
73
153
|
async function pay(input) {
|
|
74
154
|
if (typeof window === "undefined") {
|
|
75
|
-
throw new
|
|
155
|
+
throw new EscrowCheckoutError("pay(...) can only run in the browser.", "BROWSER_ONLY");
|
|
156
|
+
}
|
|
157
|
+
assertConfigured(CONFIG);
|
|
158
|
+
if (!input || !isValidPaymentToken(input.paymentToken)) {
|
|
159
|
+
throw new EscrowCheckoutError(
|
|
160
|
+
'pay(...) requires "paymentToken" (must be a non-empty string or array of non-empty strings).',
|
|
161
|
+
"INVALID_INPUT"
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (!isNonEmptyString(input.reference)) {
|
|
165
|
+
throw new EscrowCheckoutError('pay(...) requires "reference".', "INVALID_INPUT");
|
|
166
|
+
}
|
|
167
|
+
if (!isNonEmptyString(input.redirectUrl)) {
|
|
168
|
+
throw new EscrowCheckoutError('pay(...) requires "redirectUrl".', "INVALID_INPUT");
|
|
76
169
|
}
|
|
77
170
|
const [{ session }, widget] = await Promise.all([createSession(input), loadWidget()]);
|
|
78
171
|
widget({
|
|
@@ -82,11 +175,12 @@ async function pay(input) {
|
|
|
82
175
|
callback: input.callback,
|
|
83
176
|
onClose: input.onClose,
|
|
84
177
|
customerId: input.customerId,
|
|
85
|
-
publicKey: CONFIG
|
|
178
|
+
publicKey: CONFIG.publicKey,
|
|
86
179
|
...input.extra ?? {}
|
|
87
180
|
});
|
|
88
181
|
}
|
|
89
182
|
|
|
183
|
+
exports.EscrowCheckoutError = EscrowCheckoutError;
|
|
90
184
|
exports.initEscrowCheckout = initEscrowCheckout;
|
|
91
185
|
exports.pay = pay;
|
|
92
186
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export type EscrowWidget = (options: Record<string, unknown>) => void;\r\n\r\nexport interface InitConfig {\r\n publicKey: string; // publishable key only\r\n /**\r\n * Optional overrides for experts. End users don't need to set these.\r\n */\r\n scriptUrlOverride?: string;\r\n globalName?: string; // default 'EscrowCheckout'\r\n crossOrigin?: '' | 'anonymous' | 'use-credentials';\r\n}\r\n\r\nexport interface PayInput {\r\n paymentToken: string;\r\n reference: string;\r\n redirectUrl: string;\r\n logoUrl?: string;\r\n brand?: string;\r\n customerId?: string;\r\n /**\r\n * Extra fields supported by the widget.\r\n */\r\n extra?: Record<string, unknown>;\r\n callback?: (result: any) => void;\r\n onClose?: () => void;\r\n}\r\n\r\nexport interface SessionResponse {\r\n session: unknown;\r\n}\r\n\r\nconst DEFAULT_SCRIPT_URL = 'https://checkout.payluk.ng/escrow-checkout.min.js';\r\nconst DEFAULT_GLOBAL_NAME = 'EscrowCheckout';\r\n\r\ntype ResolvedConfig = Required<Pick<InitConfig, 'publicKey'>> & {\r\n scriptUrl: string;\r\n globalName: string;\r\n crossOrigin: '' | 'anonymous' | 'use-credentials';\r\n};\r\n\r\nlet CONFIG: ResolvedConfig | null = null;\r\n\r\n/**\r\n * Initialize the library once (e.g., in app bootstrap).\r\n * Hides script URL and session creation from consumers.\r\n */\r\nexport function initEscrowCheckout(config: InitConfig): void {\r\n if (!config?.publicKey) throw new Error('initEscrowCheckout: \"publicKey\" is required.');\r\n CONFIG = {\r\n publicKey: config.publicKey,\r\n scriptUrl: config.scriptUrlOverride || DEFAULT_SCRIPT_URL,\r\n globalName: config.globalName || DEFAULT_GLOBAL_NAME,\r\n crossOrigin: config.crossOrigin ?? 'anonymous'\r\n };\r\n}\r\n\r\n/**\r\n * Narrow a provided config to ResolvedConfig or throw if not initialized.\r\n */\r\nfunction assertConfigured(config: ResolvedConfig | null): asserts config is ResolvedConfig {\r\n if (!config) {\r\n throw new Error('Escrow checkout not initialized. Call initEscrowCheckout({ apiBaseUrl, publicKey }) first.');\r\n }\r\n}\r\n\r\n/**\r\n * Internal: load the widget script once and return the global function.\r\n */\r\nasync function loadWidget(): Promise<EscrowWidget> {\r\n assertConfigured(CONFIG);\r\n const { scriptUrl, globalName, crossOrigin } = CONFIG;\r\n\r\n if (typeof window === 'undefined') {\r\n throw new Error('EscrowCheckout can only be loaded in the browser.');\r\n }\r\n\r\n const win = window as any;\r\n win.__escrowCheckoutLoader ??= {};\r\n\r\n if (win.__escrowCheckoutLoader[scriptUrl]) {\r\n return win.__escrowCheckoutLoader[scriptUrl];\r\n }\r\n\r\n if (typeof win[globalName] === 'function') {\r\n const fn = win[globalName] as EscrowWidget;\r\n win.__escrowCheckoutLoader[scriptUrl] = Promise.resolve(fn);\r\n return fn;\r\n }\r\n\r\n win.__escrowCheckoutLoader[scriptUrl] = new Promise<EscrowWidget>((resolve, reject) => {\r\n const script = document.createElement('script');\r\n script.src = scriptUrl;\r\n script.async = true;\r\n if (crossOrigin) script.crossOrigin = crossOrigin;\r\n\r\n script.onload = () => {\r\n const fn = win[globalName];\r\n if (typeof fn === 'function') resolve(fn as EscrowWidget);\r\n else reject(new Error(`Escrow checkout script loaded, but window.${globalName} is not a function.`));\r\n };\r\n\r\n script.onerror = () => reject(new Error(`Failed to load escrow checkout script: ${scriptUrl}`));\r\n\r\n document.head.appendChild(script);\r\n });\r\n\r\n return win.__escrowCheckoutLoader[scriptUrl];\r\n}\r\n\r\n/**\r\n * Internal: create a checkout session by calling your API.\r\n * This is intentionally inside the lib (user doesn't implement it).\r\n */\r\nasync function createSession(input: PayInput): Promise<SessionResponse> {\r\n assertConfigured(CONFIG);\r\n const { publicKey } = CONFIG;\r\n const apiBaseUrl = publicKey.startsWith(\"pk_live_\") ? 'https://live.payluk.ng' : 'https://staging.live.payluk.ng';\r\n\r\n const resp = await fetch(`${apiBaseUrl}/v1/checkout/session`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n paymentToken: input.paymentToken,\r\n redirectUrl: input.redirectUrl,\r\n reference: input.reference,\r\n customerId: input.customerId,\r\n publicKey\r\n })\r\n });\r\n\r\n if (!resp.ok) {\r\n const err = await resp.json().catch(() => ({}));\r\n throw new Error(err.message || 'Failed to create session');\r\n }\r\n\r\n return (await resp.json()) as SessionResponse;\r\n}\r\n\r\n/**\r\n * Public API: create the session and open the widget.\r\n * Consumers only supply business inputs (no script URL, no API calls).\r\n */\r\nexport async function pay(input: PayInput): Promise<void> {\r\n if (typeof window === 'undefined') {\r\n throw new Error('pay(...) can only run in the browser.');\r\n }\r\n\r\n const [{ session }, widget] = await Promise.all([createSession(input), loadWidget()]);\r\n\r\n widget({\r\n session,\r\n logoUrl: input.logoUrl,\r\n brand: input.brand,\r\n callback: input.callback,\r\n onClose: input.onClose,\r\n customerId: input.customerId,\r\n publicKey: CONFIG?.publicKey,\r\n ...(input.extra ?? {})\r\n });\r\n}"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export type EscrowWidget = (options: Record<string, unknown>) => void;\n\nexport type EscrowCheckoutErrorCode =\n | 'NOT_INITIALIZED'\n | 'BROWSER_ONLY'\n | 'INVALID_INPUT'\n | 'WIDGET_LOAD'\n | 'NETWORK'\n | 'SESSION_CREATE'\n | 'SESSION_RESPONSE';\n\nexport class EscrowCheckoutError extends Error {\n code: EscrowCheckoutErrorCode;\n status?: number;\n details?: unknown;\n\n constructor(message: string, code: EscrowCheckoutErrorCode, options?: { status?: number; details?: unknown }) {\n super(message);\n this.name = 'EscrowCheckoutError';\n this.code = code;\n this.status = options?.status;\n this.details = options?.details;\n }\n}\n\nexport interface InitConfig {\n publicKey: string; // publishable key only\n /**\n * Optional overrides for experts. End users don't need to set these.\n */\n scriptUrlOverride?: string;\n globalName?: string; // default 'EscrowCheckout'\n crossOrigin?: '' | 'anonymous' | 'use-credentials';\n}\n\nexport interface PayInput {\n /**\n * Single escrow payment token or array of payment tokens for multi-escrow checkout.\n * When providing multiple tokens, they must all belong to the same merchant.\n */\n paymentToken: string | string[];\n reference: string;\n redirectUrl: string;\n logoUrl?: string;\n brand?: string;\n customerId?: string;\n /**\n * Extra fields supported by the widget.\n */\n extra?: Record<string, unknown>;\n callback?: (result: any) => void;\n onClose?: () => void;\n}\n\nexport interface SessionResponse {\n session: unknown;\n}\n\nconst DEFAULT_SCRIPT_URL = 'https://checkout.payluk.ng/escrow-checkout.min.js';\nconst DEFAULT_GLOBAL_NAME = 'EscrowCheckout';\n\ntype ResolvedConfig = Required<Pick<InitConfig, 'publicKey'>> & {\n scriptUrl: string;\n globalName: string;\n crossOrigin: '' | 'anonymous' | 'use-credentials';\n};\n\nlet CONFIG: ResolvedConfig | null = null;\n\n/**\n * Initialize the library once (e.g., in app bootstrap).\n * Hides script URL and session creation from consumers.\n */\nexport function initEscrowCheckout(config: InitConfig): void {\n if (!isNonEmptyString(config?.publicKey)) {\n throw new EscrowCheckoutError('initEscrowCheckout(...) requires \"publicKey\".', 'INVALID_INPUT');\n }\n CONFIG = {\n publicKey: config.publicKey.trim(),\n scriptUrl: config.scriptUrlOverride || DEFAULT_SCRIPT_URL,\n globalName: config.globalName || DEFAULT_GLOBAL_NAME,\n crossOrigin: config.crossOrigin ?? 'anonymous'\n };\n}\n\n/**\n * Narrow a provided config to ResolvedConfig or throw if not initialized.\n */\nfunction assertConfigured(config: ResolvedConfig | null): asserts config is ResolvedConfig {\n if (!config) {\n throw new EscrowCheckoutError(\n 'Escrow checkout not initialized. Call initEscrowCheckout({ publicKey }) first.',\n 'NOT_INITIALIZED'\n );\n }\n}\n\nfunction normalizeError(error: unknown): Error {\n if (error instanceof Error) return error;\n if (typeof error === 'string') return new Error(error);\n return new Error('Unknown error');\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nfunction isValidPaymentToken(value: unknown): value is string | string[] {\n // Check if it's a valid string\n if (isNonEmptyString(value)) {\n return true;\n }\n\n // Check if it's a valid array of strings\n if (Array.isArray(value)) {\n return value.length > 0 && value.every((token) => isNonEmptyString(token));\n }\n\n return false;\n}\n\nfunction isSessionResponse(value: unknown): value is SessionResponse {\n if (!value || typeof value !== 'object') return false;\n return 'session' in value;\n}\n\nasync function parseErrorBody(resp: Response): Promise<{ message?: string; details?: unknown }> {\n const text = await resp.text();\n if (!text) return {};\n\n try {\n const json = JSON.parse(text) as { message?: unknown };\n const message = typeof json?.message === 'string' ? json.message : undefined;\n return { message, details: json };\n } catch {\n return { message: text, details: text };\n }\n}\n\nasync function parseJsonResponse(resp: Response): Promise<unknown> {\n try {\n return await resp.json();\n } catch {\n throw new EscrowCheckoutError('Invalid JSON response from session endpoint.', 'SESSION_RESPONSE', {\n status: resp.status\n });\n }\n}\n\n/**\n * Internal: load the widget script once and return the global function.\n */\nasync function loadWidget(): Promise<EscrowWidget> {\n assertConfigured(CONFIG);\n const { scriptUrl, globalName, crossOrigin } = CONFIG;\n\n if (typeof window === 'undefined') {\n throw new EscrowCheckoutError('EscrowCheckout can only be loaded in the browser.', 'BROWSER_ONLY');\n }\n\n const win = window as any;\n win.__escrowCheckoutLoader ??= {};\n\n if (win.__escrowCheckoutLoader[scriptUrl]) {\n return win.__escrowCheckoutLoader[scriptUrl];\n }\n\n if (typeof win[globalName] === 'function') {\n const fn = win[globalName] as EscrowWidget;\n win.__escrowCheckoutLoader[scriptUrl] = Promise.resolve(fn);\n return fn;\n }\n\n win.__escrowCheckoutLoader[scriptUrl] = new Promise<EscrowWidget>((resolve, reject) => {\n const script = document.createElement('script');\n script.src = scriptUrl;\n script.async = true;\n if (crossOrigin) script.crossOrigin = crossOrigin;\n\n script.onload = () => {\n const fn = win[globalName];\n if (typeof fn === 'function') resolve(fn as EscrowWidget);\n else {\n reject(\n new EscrowCheckoutError(\n `Escrow checkout script loaded, but window.${globalName} is not a function.`,\n 'WIDGET_LOAD'\n )\n );\n }\n };\n\n script.onerror = () =>\n reject(new EscrowCheckoutError(`Failed to load escrow checkout script: ${scriptUrl}`, 'WIDGET_LOAD'));\n\n document.head.appendChild(script);\n });\n\n return win.__escrowCheckoutLoader[scriptUrl];\n}\n\n/**\n * Internal: create a checkout session by calling your API.\n * This is intentionally inside the lib (user doesn't implement it).\n */\nasync function createSession(input: PayInput): Promise<SessionResponse> {\n assertConfigured(CONFIG);\n const { publicKey } = CONFIG;\n const apiBaseUrl = publicKey.startsWith('pk_live_') ? 'https://live.payluk.ng' : 'https://staging.live.payluk.ng';\n let resp: Response;\n\n try {\n resp = await fetch(`${apiBaseUrl}/v1/checkout/session`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n paymentToken: input.paymentToken,\n redirectUrl: input.redirectUrl,\n reference: input.reference,\n customerId: input.customerId,\n publicKey\n })\n });\n } catch (error) {\n const normalized = normalizeError(error);\n throw new EscrowCheckoutError('Network error while creating checkout session.', 'NETWORK', {\n details: normalized.message\n });\n }\n\n if (!resp.ok) {\n const { message, details } = await parseErrorBody(resp);\n throw new EscrowCheckoutError(message || `Failed to create session (HTTP ${resp.status}).`, 'SESSION_CREATE', {\n status: resp.status,\n details\n });\n }\n\n const data = await parseJsonResponse(resp);\n if (!isSessionResponse(data)) {\n throw new EscrowCheckoutError('Session response missing \"session\".', 'SESSION_RESPONSE', {\n status: resp.status,\n details: data\n });\n }\n\n return data as SessionResponse;\n}\n\n/**\n * Public API: create the session and open the widget.\n * Consumers only supply business inputs (no script URL, no API calls).\n */\nexport async function pay(input: PayInput): Promise<void> {\n if (typeof window === 'undefined') {\n throw new EscrowCheckoutError('pay(...) can only run in the browser.', 'BROWSER_ONLY');\n }\n\n assertConfigured(CONFIG);\n if (!input || !isValidPaymentToken(input.paymentToken)) {\n throw new EscrowCheckoutError(\n 'pay(...) requires \"paymentToken\" (must be a non-empty string or array of non-empty strings).',\n 'INVALID_INPUT'\n );\n }\n if (!isNonEmptyString(input.reference)) {\n throw new EscrowCheckoutError('pay(...) requires \"reference\".', 'INVALID_INPUT');\n }\n if (!isNonEmptyString(input.redirectUrl)) {\n throw new EscrowCheckoutError('pay(...) requires \"redirectUrl\".', 'INVALID_INPUT');\n }\n\n const [{ session }, widget] = await Promise.all([createSession(input), loadWidget()]);\n\n widget({\n session,\n logoUrl: input.logoUrl,\n brand: input.brand,\n callback: input.callback,\n onClose: input.onClose,\n customerId: input.customerId,\n publicKey: CONFIG.publicKey,\n ...(input.extra ?? {})\n });\n}\n"]}
|