payluk-escrow-inline-checkout 0.2.8 → 0.2.10
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 +36 -16
- package/dist/chunk-TL6HBLNO.js +171 -0
- package/dist/chunk-TL6HBLNO.js.map +1 -0
- package/dist/index.cjs +104 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +1 -1
- package/dist/react/index.cjs +126 -37
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +28 -19
- package/dist/react/index.js.map +1 -1
- package/package.json +6 -4
- package/dist/chunk-CQKTT6UR.js +0 -89
- package/dist/chunk-CQKTT6UR.js.map +0 -1
package/README.md
CHANGED
|
@@ -241,22 +241,42 @@ import { useEscrowCheckout } from 'payluk-escrow-inline-checkout/react';
|
|
|
241
241
|
- **Initialize on the client:** If using frameworks like Next.js, call `initEscrowCheckout(...)` in a client component or in an effect.
|
|
242
242
|
- **Preloading:** The hook marks `ready` after the first successful `pay`. If you need earlier preloading, you can trigger a preparatory flow (depending on your setup).
|
|
243
243
|
|
|
244
|
-
## Error Handling
|
|
245
|
-
|
|
246
|
-
Common issues:
|
|
247
|
-
- **Not initialized:** Ensure `initEscrowCheckout({ publicKey })` is called before `pay(...)`.
|
|
248
|
-
- **Browser-only:** Do not call `pay(...)` on the server.
|
|
249
|
-
- **Network/API errors:** If the session endpoint fails, `pay(...)` will reject with
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
244
|
+
## Error Handling
|
|
245
|
+
|
|
246
|
+
Common issues:
|
|
247
|
+
- **Not initialized:** Ensure `initEscrowCheckout({ publicKey })` is called before `pay(...)`.
|
|
248
|
+
- **Browser-only:** Do not call `pay(...)` on the server.
|
|
249
|
+
- **Network/API errors:** If the session endpoint fails, `pay(...)` will reject with an `EscrowCheckoutError` that includes a `code`, optional HTTP `status`, and `details`.
|
|
250
|
+
|
|
251
|
+
`EscrowCheckoutError` codes:
|
|
252
|
+
- `NOT_INITIALIZED`
|
|
253
|
+
- `BROWSER_ONLY`
|
|
254
|
+
- `INVALID_INPUT`
|
|
255
|
+
- `WIDGET_LOAD`
|
|
256
|
+
- `NETWORK`
|
|
257
|
+
- `SESSION_CREATE`
|
|
258
|
+
- `SESSION_RESPONSE`
|
|
259
|
+
|
|
260
|
+
You can import the error class if you want stricter checks in TypeScript:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { EscrowCheckoutError } from 'payluk-escrow-inline-checkout';
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Example:**
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
try {
|
|
270
|
+
await pay({ /* ... */ });
|
|
271
|
+
} catch (err) {
|
|
272
|
+
if (err instanceof EscrowCheckoutError) {
|
|
273
|
+
console.error(err.code, err.status, err.message);
|
|
274
|
+
alert(err.message);
|
|
275
|
+
} else {
|
|
276
|
+
alert('Checkout failed');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
260
280
|
|
|
261
281
|
## Security
|
|
262
282
|
|
|
@@ -0,0 +1,171 @@
|
|
|
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 isSessionResponse(value) {
|
|
42
|
+
if (!value || typeof value !== "object") return false;
|
|
43
|
+
return "session" in value;
|
|
44
|
+
}
|
|
45
|
+
async function parseErrorBody(resp) {
|
|
46
|
+
const text = await resp.text();
|
|
47
|
+
if (!text) return {};
|
|
48
|
+
try {
|
|
49
|
+
const json = JSON.parse(text);
|
|
50
|
+
const message = typeof json?.message === "string" ? json.message : void 0;
|
|
51
|
+
return { message, details: json };
|
|
52
|
+
} catch {
|
|
53
|
+
return { message: text, details: text };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function parseJsonResponse(resp) {
|
|
57
|
+
try {
|
|
58
|
+
return await resp.json();
|
|
59
|
+
} catch {
|
|
60
|
+
throw new EscrowCheckoutError("Invalid JSON response from session endpoint.", "SESSION_RESPONSE", {
|
|
61
|
+
status: resp.status
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function loadWidget() {
|
|
66
|
+
assertConfigured(CONFIG);
|
|
67
|
+
const { scriptUrl, globalName, crossOrigin } = CONFIG;
|
|
68
|
+
if (typeof window === "undefined") {
|
|
69
|
+
throw new EscrowCheckoutError("EscrowCheckout can only be loaded in the browser.", "BROWSER_ONLY");
|
|
70
|
+
}
|
|
71
|
+
const win = window;
|
|
72
|
+
win.__escrowCheckoutLoader ?? (win.__escrowCheckoutLoader = {});
|
|
73
|
+
if (win.__escrowCheckoutLoader[scriptUrl]) {
|
|
74
|
+
return win.__escrowCheckoutLoader[scriptUrl];
|
|
75
|
+
}
|
|
76
|
+
if (typeof win[globalName] === "function") {
|
|
77
|
+
const fn = win[globalName];
|
|
78
|
+
win.__escrowCheckoutLoader[scriptUrl] = Promise.resolve(fn);
|
|
79
|
+
return fn;
|
|
80
|
+
}
|
|
81
|
+
win.__escrowCheckoutLoader[scriptUrl] = new Promise((resolve, reject) => {
|
|
82
|
+
const script = document.createElement("script");
|
|
83
|
+
script.src = scriptUrl;
|
|
84
|
+
script.async = true;
|
|
85
|
+
if (crossOrigin) script.crossOrigin = crossOrigin;
|
|
86
|
+
script.onload = () => {
|
|
87
|
+
const fn = win[globalName];
|
|
88
|
+
if (typeof fn === "function") resolve(fn);
|
|
89
|
+
else {
|
|
90
|
+
reject(
|
|
91
|
+
new EscrowCheckoutError(
|
|
92
|
+
`Escrow checkout script loaded, but window.${globalName} is not a function.`,
|
|
93
|
+
"WIDGET_LOAD"
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
script.onerror = () => reject(new EscrowCheckoutError(`Failed to load escrow checkout script: ${scriptUrl}`, "WIDGET_LOAD"));
|
|
99
|
+
document.head.appendChild(script);
|
|
100
|
+
});
|
|
101
|
+
return win.__escrowCheckoutLoader[scriptUrl];
|
|
102
|
+
}
|
|
103
|
+
async function createSession(input) {
|
|
104
|
+
assertConfigured(CONFIG);
|
|
105
|
+
const { publicKey } = CONFIG;
|
|
106
|
+
const apiBaseUrl = publicKey.startsWith("pk_live_") ? "https://live.payluk.ng" : "https://staging.live.payluk.ng";
|
|
107
|
+
let resp;
|
|
108
|
+
try {
|
|
109
|
+
resp = await fetch(`${apiBaseUrl}/v1/checkout/session`, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: { "Content-Type": "application/json" },
|
|
112
|
+
body: JSON.stringify({
|
|
113
|
+
paymentToken: input.paymentToken,
|
|
114
|
+
redirectUrl: input.redirectUrl,
|
|
115
|
+
reference: input.reference,
|
|
116
|
+
customerId: input.customerId,
|
|
117
|
+
publicKey
|
|
118
|
+
})
|
|
119
|
+
});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
const normalized = normalizeError(error);
|
|
122
|
+
throw new EscrowCheckoutError("Network error while creating checkout session.", "NETWORK", {
|
|
123
|
+
details: normalized.message
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (!resp.ok) {
|
|
127
|
+
const { message, details } = await parseErrorBody(resp);
|
|
128
|
+
throw new EscrowCheckoutError(message || `Failed to create session (HTTP ${resp.status}).`, "SESSION_CREATE", {
|
|
129
|
+
status: resp.status,
|
|
130
|
+
details
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const data = await parseJsonResponse(resp);
|
|
134
|
+
if (!isSessionResponse(data)) {
|
|
135
|
+
throw new EscrowCheckoutError('Session response missing "session".', "SESSION_RESPONSE", {
|
|
136
|
+
status: resp.status,
|
|
137
|
+
details: data
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return data;
|
|
141
|
+
}
|
|
142
|
+
async function pay(input) {
|
|
143
|
+
if (typeof window === "undefined") {
|
|
144
|
+
throw new EscrowCheckoutError("pay(...) can only run in the browser.", "BROWSER_ONLY");
|
|
145
|
+
}
|
|
146
|
+
assertConfigured(CONFIG);
|
|
147
|
+
if (!input || !isNonEmptyString(input.paymentToken)) {
|
|
148
|
+
throw new EscrowCheckoutError('pay(...) requires "paymentToken".', "INVALID_INPUT");
|
|
149
|
+
}
|
|
150
|
+
if (!isNonEmptyString(input.reference)) {
|
|
151
|
+
throw new EscrowCheckoutError('pay(...) requires "reference".', "INVALID_INPUT");
|
|
152
|
+
}
|
|
153
|
+
if (!isNonEmptyString(input.redirectUrl)) {
|
|
154
|
+
throw new EscrowCheckoutError('pay(...) requires "redirectUrl".', "INVALID_INPUT");
|
|
155
|
+
}
|
|
156
|
+
const [{ session }, widget] = await Promise.all([createSession(input), loadWidget()]);
|
|
157
|
+
widget({
|
|
158
|
+
session,
|
|
159
|
+
logoUrl: input.logoUrl,
|
|
160
|
+
brand: input.brand,
|
|
161
|
+
callback: input.callback,
|
|
162
|
+
onClose: input.onClose,
|
|
163
|
+
customerId: input.customerId,
|
|
164
|
+
publicKey: CONFIG.publicKey,
|
|
165
|
+
...input.extra ?? {}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export { EscrowCheckoutError, initEscrowCheckout, pay };
|
|
170
|
+
//# sourceMappingURL=chunk-TL6HBLNO.js.map
|
|
171
|
+
//# sourceMappingURL=chunk-TL6HBLNO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"chunk-TL6HBLNO.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;\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 {\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\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 {\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 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;\r\n\r\n if (typeof window === 'undefined') {\n throw new EscrowCheckoutError('EscrowCheckout can only be loaded in the browser.', 'BROWSER_ONLY');\n }\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);\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\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> {\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\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> {\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 || !isNonEmptyString(input.paymentToken)) {\n throw new EscrowCheckoutError('pay(...) requires \"paymentToken\".', 'INVALID_INPUT');\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,49 @@ 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 isSessionResponse(value) {
|
|
44
|
+
if (!value || typeof value !== "object") return false;
|
|
45
|
+
return "session" in value;
|
|
46
|
+
}
|
|
47
|
+
async function parseErrorBody(resp) {
|
|
48
|
+
const text = await resp.text();
|
|
49
|
+
if (!text) return {};
|
|
50
|
+
try {
|
|
51
|
+
const json = JSON.parse(text);
|
|
52
|
+
const message = typeof json?.message === "string" ? json.message : void 0;
|
|
53
|
+
return { message, details: json };
|
|
54
|
+
} catch {
|
|
55
|
+
return { message: text, details: text };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function parseJsonResponse(resp) {
|
|
59
|
+
try {
|
|
60
|
+
return await resp.json();
|
|
61
|
+
} catch {
|
|
62
|
+
throw new EscrowCheckoutError("Invalid JSON response from session endpoint.", "SESSION_RESPONSE", {
|
|
63
|
+
status: resp.status
|
|
64
|
+
});
|
|
19
65
|
}
|
|
20
66
|
}
|
|
21
67
|
async function loadWidget() {
|
|
22
68
|
assertConfigured(CONFIG);
|
|
23
69
|
const { scriptUrl, globalName, crossOrigin } = CONFIG;
|
|
24
70
|
if (typeof window === "undefined") {
|
|
25
|
-
throw new
|
|
71
|
+
throw new EscrowCheckoutError("EscrowCheckout can only be loaded in the browser.", "BROWSER_ONLY");
|
|
26
72
|
}
|
|
27
73
|
const win = window;
|
|
28
74
|
win.__escrowCheckoutLoader ?? (win.__escrowCheckoutLoader = {});
|
|
@@ -42,9 +88,16 @@ async function loadWidget() {
|
|
|
42
88
|
script.onload = () => {
|
|
43
89
|
const fn = win[globalName];
|
|
44
90
|
if (typeof fn === "function") resolve(fn);
|
|
45
|
-
else
|
|
91
|
+
else {
|
|
92
|
+
reject(
|
|
93
|
+
new EscrowCheckoutError(
|
|
94
|
+
`Escrow checkout script loaded, but window.${globalName} is not a function.`,
|
|
95
|
+
"WIDGET_LOAD"
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
46
99
|
};
|
|
47
|
-
script.onerror = () => reject(new
|
|
100
|
+
script.onerror = () => reject(new EscrowCheckoutError(`Failed to load escrow checkout script: ${scriptUrl}`, "WIDGET_LOAD"));
|
|
48
101
|
document.head.appendChild(script);
|
|
49
102
|
});
|
|
50
103
|
return win.__escrowCheckoutLoader[scriptUrl];
|
|
@@ -53,26 +106,54 @@ async function createSession(input) {
|
|
|
53
106
|
assertConfigured(CONFIG);
|
|
54
107
|
const { publicKey } = CONFIG;
|
|
55
108
|
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
|
-
|
|
109
|
+
let resp;
|
|
110
|
+
try {
|
|
111
|
+
resp = await fetch(`${apiBaseUrl}/v1/checkout/session`, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: { "Content-Type": "application/json" },
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
paymentToken: input.paymentToken,
|
|
116
|
+
redirectUrl: input.redirectUrl,
|
|
117
|
+
reference: input.reference,
|
|
118
|
+
customerId: input.customerId,
|
|
119
|
+
publicKey
|
|
120
|
+
})
|
|
121
|
+
});
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const normalized = normalizeError(error);
|
|
124
|
+
throw new EscrowCheckoutError("Network error while creating checkout session.", "NETWORK", {
|
|
125
|
+
details: normalized.message
|
|
126
|
+
});
|
|
127
|
+
}
|
|
67
128
|
if (!resp.ok) {
|
|
68
|
-
const
|
|
69
|
-
throw new
|
|
129
|
+
const { message, details } = await parseErrorBody(resp);
|
|
130
|
+
throw new EscrowCheckoutError(message || `Failed to create session (HTTP ${resp.status}).`, "SESSION_CREATE", {
|
|
131
|
+
status: resp.status,
|
|
132
|
+
details
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const data = await parseJsonResponse(resp);
|
|
136
|
+
if (!isSessionResponse(data)) {
|
|
137
|
+
throw new EscrowCheckoutError('Session response missing "session".', "SESSION_RESPONSE", {
|
|
138
|
+
status: resp.status,
|
|
139
|
+
details: data
|
|
140
|
+
});
|
|
70
141
|
}
|
|
71
|
-
return
|
|
142
|
+
return data;
|
|
72
143
|
}
|
|
73
144
|
async function pay(input) {
|
|
74
145
|
if (typeof window === "undefined") {
|
|
75
|
-
throw new
|
|
146
|
+
throw new EscrowCheckoutError("pay(...) can only run in the browser.", "BROWSER_ONLY");
|
|
147
|
+
}
|
|
148
|
+
assertConfigured(CONFIG);
|
|
149
|
+
if (!input || !isNonEmptyString(input.paymentToken)) {
|
|
150
|
+
throw new EscrowCheckoutError('pay(...) requires "paymentToken".', "INVALID_INPUT");
|
|
151
|
+
}
|
|
152
|
+
if (!isNonEmptyString(input.reference)) {
|
|
153
|
+
throw new EscrowCheckoutError('pay(...) requires "reference".', "INVALID_INPUT");
|
|
154
|
+
}
|
|
155
|
+
if (!isNonEmptyString(input.redirectUrl)) {
|
|
156
|
+
throw new EscrowCheckoutError('pay(...) requires "redirectUrl".', "INVALID_INPUT");
|
|
76
157
|
}
|
|
77
158
|
const [{ session }, widget] = await Promise.all([createSession(input), loadWidget()]);
|
|
78
159
|
widget({
|
|
@@ -82,10 +163,12 @@ async function pay(input) {
|
|
|
82
163
|
callback: input.callback,
|
|
83
164
|
onClose: input.onClose,
|
|
84
165
|
customerId: input.customerId,
|
|
166
|
+
publicKey: CONFIG.publicKey,
|
|
85
167
|
...input.extra ?? {}
|
|
86
168
|
});
|
|
87
169
|
}
|
|
88
170
|
|
|
171
|
+
exports.EscrowCheckoutError = EscrowCheckoutError;
|
|
89
172
|
exports.initEscrowCheckout = initEscrowCheckout;
|
|
90
173
|
exports.pay = pay;
|
|
91
174
|
//# 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 ...(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;\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 {\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\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 {\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 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;\r\n\r\n if (typeof window === 'undefined') {\n throw new EscrowCheckoutError('EscrowCheckout can only be loaded in the browser.', 'BROWSER_ONLY');\n }\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);\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\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> {\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\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> {\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 || !isNonEmptyString(input.paymentToken)) {\n throw new EscrowCheckoutError('pay(...) requires \"paymentToken\".', 'INVALID_INPUT');\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.d.cts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
type EscrowWidget = (options: Record<string, unknown>) => void;
|
|
2
|
+
type EscrowCheckoutErrorCode = 'NOT_INITIALIZED' | 'BROWSER_ONLY' | 'INVALID_INPUT' | 'WIDGET_LOAD' | 'NETWORK' | 'SESSION_CREATE' | 'SESSION_RESPONSE';
|
|
3
|
+
declare class EscrowCheckoutError extends Error {
|
|
4
|
+
code: EscrowCheckoutErrorCode;
|
|
5
|
+
status?: number;
|
|
6
|
+
details?: unknown;
|
|
7
|
+
constructor(message: string, code: EscrowCheckoutErrorCode, options?: {
|
|
8
|
+
status?: number;
|
|
9
|
+
details?: unknown;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
2
12
|
interface InitConfig {
|
|
3
13
|
publicKey: string;
|
|
4
14
|
/**
|
|
@@ -36,4 +46,4 @@ declare function initEscrowCheckout(config: InitConfig): void;
|
|
|
36
46
|
*/
|
|
37
47
|
declare function pay(input: PayInput): Promise<void>;
|
|
38
48
|
|
|
39
|
-
export { type EscrowWidget, type InitConfig, type PayInput, type SessionResponse, initEscrowCheckout, pay };
|
|
49
|
+
export { EscrowCheckoutError, type EscrowCheckoutErrorCode, type EscrowWidget, type InitConfig, type PayInput, type SessionResponse, initEscrowCheckout, pay };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
type EscrowWidget = (options: Record<string, unknown>) => void;
|
|
2
|
+
type EscrowCheckoutErrorCode = 'NOT_INITIALIZED' | 'BROWSER_ONLY' | 'INVALID_INPUT' | 'WIDGET_LOAD' | 'NETWORK' | 'SESSION_CREATE' | 'SESSION_RESPONSE';
|
|
3
|
+
declare class EscrowCheckoutError extends Error {
|
|
4
|
+
code: EscrowCheckoutErrorCode;
|
|
5
|
+
status?: number;
|
|
6
|
+
details?: unknown;
|
|
7
|
+
constructor(message: string, code: EscrowCheckoutErrorCode, options?: {
|
|
8
|
+
status?: number;
|
|
9
|
+
details?: unknown;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
2
12
|
interface InitConfig {
|
|
3
13
|
publicKey: string;
|
|
4
14
|
/**
|
|
@@ -36,4 +46,4 @@ declare function initEscrowCheckout(config: InitConfig): void;
|
|
|
36
46
|
*/
|
|
37
47
|
declare function pay(input: PayInput): Promise<void>;
|
|
38
48
|
|
|
39
|
-
export { type EscrowWidget, type InitConfig, type PayInput, type SessionResponse, initEscrowCheckout, pay };
|
|
49
|
+
export { EscrowCheckoutError, type EscrowCheckoutErrorCode, type EscrowWidget, type InitConfig, type PayInput, type SessionResponse, initEscrowCheckout, pay };
|
package/dist/index.js
CHANGED
package/dist/react/index.cjs
CHANGED
|
@@ -6,17 +6,61 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
6
6
|
// src/react/useEscrowCheckout.ts
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
|
+
var EscrowCheckoutError = class extends Error {
|
|
10
|
+
constructor(message, code, options) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "EscrowCheckoutError";
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.status = options?.status;
|
|
15
|
+
this.details = options?.details;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
9
18
|
var CONFIG = null;
|
|
10
19
|
function assertConfigured(config) {
|
|
11
20
|
{
|
|
12
|
-
throw new
|
|
21
|
+
throw new EscrowCheckoutError(
|
|
22
|
+
"Escrow checkout not initialized. Call initEscrowCheckout({ publicKey }) first.",
|
|
23
|
+
"NOT_INITIALIZED"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function normalizeError(error) {
|
|
28
|
+
if (error instanceof Error) return error;
|
|
29
|
+
if (typeof error === "string") return new Error(error);
|
|
30
|
+
return new Error("Unknown error");
|
|
31
|
+
}
|
|
32
|
+
function isNonEmptyString(value) {
|
|
33
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
34
|
+
}
|
|
35
|
+
function isSessionResponse(value) {
|
|
36
|
+
if (!value || typeof value !== "object") return false;
|
|
37
|
+
return "session" in value;
|
|
38
|
+
}
|
|
39
|
+
async function parseErrorBody(resp) {
|
|
40
|
+
const text = await resp.text();
|
|
41
|
+
if (!text) return {};
|
|
42
|
+
try {
|
|
43
|
+
const json = JSON.parse(text);
|
|
44
|
+
const message = typeof json?.message === "string" ? json.message : void 0;
|
|
45
|
+
return { message, details: json };
|
|
46
|
+
} catch {
|
|
47
|
+
return { message: text, details: text };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function parseJsonResponse(resp) {
|
|
51
|
+
try {
|
|
52
|
+
return await resp.json();
|
|
53
|
+
} catch {
|
|
54
|
+
throw new EscrowCheckoutError("Invalid JSON response from session endpoint.", "SESSION_RESPONSE", {
|
|
55
|
+
status: resp.status
|
|
56
|
+
});
|
|
13
57
|
}
|
|
14
58
|
}
|
|
15
59
|
async function loadWidget() {
|
|
16
60
|
assertConfigured();
|
|
17
61
|
const { scriptUrl, globalName, crossOrigin } = CONFIG;
|
|
18
62
|
if (typeof window === "undefined") {
|
|
19
|
-
throw new
|
|
63
|
+
throw new EscrowCheckoutError("EscrowCheckout can only be loaded in the browser.", "BROWSER_ONLY");
|
|
20
64
|
}
|
|
21
65
|
const win = window;
|
|
22
66
|
win.__escrowCheckoutLoader ?? (win.__escrowCheckoutLoader = {});
|
|
@@ -36,9 +80,16 @@ async function loadWidget() {
|
|
|
36
80
|
script.onload = () => {
|
|
37
81
|
const fn = win[globalName];
|
|
38
82
|
if (typeof fn === "function") resolve(fn);
|
|
39
|
-
else
|
|
83
|
+
else {
|
|
84
|
+
reject(
|
|
85
|
+
new EscrowCheckoutError(
|
|
86
|
+
`Escrow checkout script loaded, but window.${globalName} is not a function.`,
|
|
87
|
+
"WIDGET_LOAD"
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
40
91
|
};
|
|
41
|
-
script.onerror = () => reject(new
|
|
92
|
+
script.onerror = () => reject(new EscrowCheckoutError(`Failed to load escrow checkout script: ${scriptUrl}`, "WIDGET_LOAD"));
|
|
42
93
|
document.head.appendChild(script);
|
|
43
94
|
});
|
|
44
95
|
return win.__escrowCheckoutLoader[scriptUrl];
|
|
@@ -47,26 +98,54 @@ async function createSession(input) {
|
|
|
47
98
|
assertConfigured();
|
|
48
99
|
const { publicKey } = CONFIG;
|
|
49
100
|
const apiBaseUrl = publicKey.startsWith("pk_live_") ? "https://live.payluk.ng" : "https://staging.live.payluk.ng";
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
101
|
+
let resp;
|
|
102
|
+
try {
|
|
103
|
+
resp = await fetch(`${apiBaseUrl}/v1/checkout/session`, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: { "Content-Type": "application/json" },
|
|
106
|
+
body: JSON.stringify({
|
|
107
|
+
paymentToken: input.paymentToken,
|
|
108
|
+
redirectUrl: input.redirectUrl,
|
|
109
|
+
reference: input.reference,
|
|
110
|
+
customerId: input.customerId,
|
|
111
|
+
publicKey
|
|
112
|
+
})
|
|
113
|
+
});
|
|
114
|
+
} catch (error) {
|
|
115
|
+
const normalized = normalizeError(error);
|
|
116
|
+
throw new EscrowCheckoutError("Network error while creating checkout session.", "NETWORK", {
|
|
117
|
+
details: normalized.message
|
|
118
|
+
});
|
|
119
|
+
}
|
|
61
120
|
if (!resp.ok) {
|
|
62
|
-
const
|
|
63
|
-
throw new
|
|
121
|
+
const { message, details } = await parseErrorBody(resp);
|
|
122
|
+
throw new EscrowCheckoutError(message || `Failed to create session (HTTP ${resp.status}).`, "SESSION_CREATE", {
|
|
123
|
+
status: resp.status,
|
|
124
|
+
details
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
const data = await parseJsonResponse(resp);
|
|
128
|
+
if (!isSessionResponse(data)) {
|
|
129
|
+
throw new EscrowCheckoutError('Session response missing "session".', "SESSION_RESPONSE", {
|
|
130
|
+
status: resp.status,
|
|
131
|
+
details: data
|
|
132
|
+
});
|
|
64
133
|
}
|
|
65
|
-
return
|
|
134
|
+
return data;
|
|
66
135
|
}
|
|
67
136
|
async function pay(input) {
|
|
68
137
|
if (typeof window === "undefined") {
|
|
69
|
-
throw new
|
|
138
|
+
throw new EscrowCheckoutError("pay(...) can only run in the browser.", "BROWSER_ONLY");
|
|
139
|
+
}
|
|
140
|
+
assertConfigured();
|
|
141
|
+
if (!input || !isNonEmptyString(input.paymentToken)) {
|
|
142
|
+
throw new EscrowCheckoutError('pay(...) requires "paymentToken".', "INVALID_INPUT");
|
|
143
|
+
}
|
|
144
|
+
if (!isNonEmptyString(input.reference)) {
|
|
145
|
+
throw new EscrowCheckoutError('pay(...) requires "reference".', "INVALID_INPUT");
|
|
146
|
+
}
|
|
147
|
+
if (!isNonEmptyString(input.redirectUrl)) {
|
|
148
|
+
throw new EscrowCheckoutError('pay(...) requires "redirectUrl".', "INVALID_INPUT");
|
|
70
149
|
}
|
|
71
150
|
const [{ session }, widget] = await Promise.all([createSession(input), loadWidget()]);
|
|
72
151
|
widget({
|
|
@@ -76,6 +155,7 @@ async function pay(input) {
|
|
|
76
155
|
callback: input.callback,
|
|
77
156
|
onClose: input.onClose,
|
|
78
157
|
customerId: input.customerId,
|
|
158
|
+
publicKey: CONFIG.publicKey,
|
|
79
159
|
...input.extra ?? {}
|
|
80
160
|
});
|
|
81
161
|
}
|
|
@@ -83,8 +163,13 @@ async function pay(input) {
|
|
|
83
163
|
// src/react/useEscrowCheckout.ts
|
|
84
164
|
function useEscrowCheckout() {
|
|
85
165
|
const [ready, setReady] = react.useState(false);
|
|
86
|
-
const [loading, setLoading] = react.useState(
|
|
166
|
+
const [loading, setLoading] = react.useState(false);
|
|
87
167
|
const [error, setError] = react.useState(null);
|
|
168
|
+
const normalizeError2 = react.useCallback((err) => {
|
|
169
|
+
if (err instanceof Error) return err;
|
|
170
|
+
if (typeof err === "string") return new Error(err);
|
|
171
|
+
return new Error("Unknown error");
|
|
172
|
+
}, []);
|
|
88
173
|
const pay2 = react.useCallback(async (input) => {
|
|
89
174
|
setLoading(true);
|
|
90
175
|
try {
|
|
@@ -92,12 +177,13 @@ function useEscrowCheckout() {
|
|
|
92
177
|
setReady(true);
|
|
93
178
|
setError(null);
|
|
94
179
|
} catch (e) {
|
|
95
|
-
|
|
96
|
-
|
|
180
|
+
const normalized = normalizeError2(e);
|
|
181
|
+
setError(normalized);
|
|
182
|
+
throw normalized;
|
|
97
183
|
} finally {
|
|
98
184
|
setLoading(false);
|
|
99
185
|
}
|
|
100
|
-
}, []);
|
|
186
|
+
}, [normalizeError2]);
|
|
101
187
|
react.useEffect(() => {
|
|
102
188
|
if (typeof window === "undefined") {
|
|
103
189
|
setLoading(false);
|
|
@@ -120,28 +206,31 @@ function EscrowCheckoutButton({
|
|
|
120
206
|
title,
|
|
121
207
|
customerId
|
|
122
208
|
}) {
|
|
123
|
-
const {
|
|
209
|
+
const { loading, error, pay: pay2 } = useEscrowCheckout();
|
|
124
210
|
const handleClick = react.useCallback(async () => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
211
|
+
try {
|
|
212
|
+
await pay2({
|
|
213
|
+
paymentToken,
|
|
214
|
+
reference,
|
|
215
|
+
redirectUrl,
|
|
216
|
+
brand,
|
|
217
|
+
logoUrl,
|
|
218
|
+
callback,
|
|
219
|
+
onClose,
|
|
220
|
+
extra,
|
|
221
|
+
customerId
|
|
222
|
+
});
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
136
225
|
}, [pay2, paymentToken, reference, redirectUrl, brand, logoUrl, callback, onClose, extra, customerId]);
|
|
137
226
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
138
227
|
"button",
|
|
139
228
|
{
|
|
140
229
|
type: "button",
|
|
141
230
|
onClick: handleClick,
|
|
142
|
-
disabled: disabled || loading
|
|
231
|
+
disabled: disabled || loading,
|
|
143
232
|
className,
|
|
144
|
-
"aria-disabled": disabled || loading
|
|
233
|
+
"aria-disabled": disabled || loading,
|
|
145
234
|
title: title ?? (error ? error.message : void 0),
|
|
146
235
|
children
|
|
147
236
|
}
|
package/dist/react/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts","../../src/react/useEscrowCheckout.ts","../../src/react/EscrowCheckoutButton.tsx"],"names":["useState","pay","useCallback","useEffect","useMemo","jsx"],"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 ...(input.extra ?? {})\r\n });\r\n}","import { useCallback, useEffect, useMemo, useState } from 'react';\r\nimport { pay as corePay } from '../index';\r\n\r\nexport interface UseEscrowCheckoutResult {\r\n ready: boolean;\r\n loading: boolean;\r\n error: Error | null;\r\n pay: typeof corePay;\r\n}\r\n\r\n/**\r\n * React hook that preloads the widget script (implicitly via pay) and exposes pay(...)\r\n * Since the library abstracts script loading and session creation, the hook is thin.\r\n */\r\nexport function useEscrowCheckout(): UseEscrowCheckoutResult {\r\n const [ready, setReady] = useState(false);\r\n const [loading, setLoading] = useState<boolean>(() => typeof window !== 'undefined');\r\n const [error, setError] = useState<Error | null>(null);\r\n\r\n // Lazy mark \"ready\" after first successful pay or script load attempt.\r\n // If you want to preload the script earlier, you may trigger a no-op pay with a dry-run endpoint on your side.\r\n\r\n // Provide a stable pay function that reports loading state.\r\n const pay = useCallback<typeof corePay>(async (input) => {\r\n setLoading(true);\r\n try {\r\n await corePay(input);\r\n setReady(true);\r\n setError(null);\r\n } catch (e) {\r\n setError(e as Error);\r\n throw e;\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, []);\r\n\r\n // In SSR, mark not loading\r\n useEffect(() => {\r\n if (typeof window === 'undefined') {\r\n setLoading(false);\r\n }\r\n }, []);\r\n\r\n return useMemo(() => ({ ready, loading, error, pay }), [ready, loading, error, pay]);\r\n}","import React, { useCallback } from 'react';\r\nimport { useEscrowCheckout } from './useEscrowCheckout';\r\n\r\nexport interface EscrowCheckoutButtonProps {\r\n paymentToken: string;\r\n reference: string;\r\n redirectUrl: string;\r\n children?: React.ReactNode;\r\n disabled?: boolean;\r\n className?: string;\r\n title?: string;\r\n\r\n brand?: string;\r\n customerId?: string;\r\n logoUrl?: string;\r\n callback?: (result: any) => void;\r\n onClose?: () => void;\r\n extra?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Button that triggers pay(...) with your provided inputs.\r\n * The library handles both session creation and widget loading.\r\n */\r\nexport function EscrowCheckoutButton({\r\n paymentToken,\r\n reference,\r\n redirectUrl,\r\n brand,\r\n logoUrl,\r\n callback,\r\n onClose,\r\n extra,\r\n children = 'Pay',\r\n disabled,\r\n className,\r\n title,\r\n customerId\r\n }: EscrowCheckoutButtonProps) {\r\n const { ready, loading, error, pay } = useEscrowCheckout();\r\n\r\n const handleClick = useCallback(async () => {\r\n await pay({\r\n paymentToken,\r\n reference,\r\n redirectUrl,\r\n brand,\r\n logoUrl,\r\n callback,\r\n onClose,\r\n extra,\r\n customerId\r\n });\r\n }, [pay, paymentToken, reference, redirectUrl, brand, logoUrl, callback, onClose, extra, customerId]);\r\n\r\n return (\r\n <button\r\n type=\"button\"\r\n onClick={handleClick}\r\n disabled={disabled || loading || !ready /* ready becomes true after first successful load */}\r\n className={className}\r\n aria-disabled={disabled || loading || !ready}\r\n title={title ?? (error ? error.message : undefined)}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}"]}
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/react/useEscrowCheckout.ts","../../src/react/EscrowCheckoutButton.tsx"],"names":["useState","normalizeError","useCallback","pay","useEffect","useMemo","jsx"],"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;\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 {\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\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 {\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 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;\r\n\r\n if (typeof window === 'undefined') {\n throw new EscrowCheckoutError('EscrowCheckout can only be loaded in the browser.', 'BROWSER_ONLY');\n }\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);\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\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> {\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\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> {\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 || !isNonEmptyString(input.paymentToken)) {\n throw new EscrowCheckoutError('pay(...) requires \"paymentToken\".', 'INVALID_INPUT');\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","import { useCallback, useEffect, useMemo, useState } from 'react';\r\nimport { pay as corePay } from '../index';\r\n\r\nexport interface UseEscrowCheckoutResult {\r\n ready: boolean;\r\n loading: boolean;\r\n error: Error | null;\r\n pay: typeof corePay;\r\n}\r\n\r\n/**\r\n * React hook that preloads the widget script (implicitly via pay) and exposes pay(...)\r\n * Since the library abstracts script loading and session creation, the hook is thin.\r\n */\r\nexport function useEscrowCheckout(): UseEscrowCheckoutResult {\n const [ready, setReady] = useState(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const normalizeError = useCallback((err: unknown): Error => {\n if (err instanceof Error) return err;\n if (typeof err === 'string') return new Error(err);\n return new Error('Unknown error');\n }, []);\n\n // Lazy mark \"ready\" after first successful pay or script load attempt.\n // If you want to preload the script earlier, you may trigger a no-op pay with a dry-run endpoint on your side.\n\n // Provide a stable pay function that reports loading state.\n const pay = useCallback<typeof corePay>(async (input) => {\n setLoading(true);\r\n try {\r\n await corePay(input);\n setReady(true);\n setError(null);\n } catch (e) {\n const normalized = normalizeError(e);\n setError(normalized);\n throw normalized;\n } finally {\n setLoading(false);\n }\n }, [normalizeError]);\n\r\n // In SSR, mark not loading\n useEffect(() => {\n if (typeof window === 'undefined') {\n setLoading(false);\n }\n }, []);\n\r\n return useMemo(() => ({ ready, loading, error, pay }), [ready, loading, error, pay]);\r\n}\n","import React, { useCallback } from 'react';\r\nimport { useEscrowCheckout } from './useEscrowCheckout';\r\n\r\nexport interface EscrowCheckoutButtonProps {\r\n paymentToken: string;\r\n reference: string;\r\n redirectUrl: string;\r\n children?: React.ReactNode;\r\n disabled?: boolean;\r\n className?: string;\r\n title?: string;\r\n\r\n brand?: string;\r\n customerId?: string;\r\n logoUrl?: string;\r\n callback?: (result: any) => void;\r\n onClose?: () => void;\r\n extra?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Button that triggers pay(...) with your provided inputs.\r\n * The library handles both session creation and widget loading.\r\n */\r\nexport function EscrowCheckoutButton({\r\n paymentToken,\r\n reference,\r\n redirectUrl,\r\n brand,\r\n logoUrl,\r\n callback,\r\n onClose,\r\n extra,\r\n children = 'Pay',\r\n disabled,\r\n className,\r\n title,\r\n customerId\r\n }: EscrowCheckoutButtonProps) {\r\n const { loading, error, pay } = useEscrowCheckout();\n\n const handleClick = useCallback(async () => {\n try {\n await pay({\n paymentToken,\n reference,\n redirectUrl,\n brand,\n logoUrl,\n callback,\n onClose,\n extra,\n customerId\n });\n } catch {\n // Error state is already exposed via the hook.\n }\n }, [pay, paymentToken, reference, redirectUrl, brand, logoUrl, callback, onClose, extra, customerId]);\n\r\n return (\r\n <button\r\n type=\"button\"\n onClick={handleClick}\n disabled={disabled || loading}\n className={className}\n aria-disabled={disabled || loading}\n title={title ?? (error ? error.message : undefined)}\n >\n {children}\n </button>\n );\r\n}\n"]}
|
package/dist/react/index.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { pay } from '../chunk-
|
|
1
|
+
import { pay } from '../chunk-TL6HBLNO.js';
|
|
2
2
|
import { useState, useCallback, useEffect, useMemo } from 'react';
|
|
3
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
function useEscrowCheckout() {
|
|
6
6
|
const [ready, setReady] = useState(false);
|
|
7
|
-
const [loading, setLoading] = useState(
|
|
7
|
+
const [loading, setLoading] = useState(false);
|
|
8
8
|
const [error, setError] = useState(null);
|
|
9
|
+
const normalizeError = useCallback((err) => {
|
|
10
|
+
if (err instanceof Error) return err;
|
|
11
|
+
if (typeof err === "string") return new Error(err);
|
|
12
|
+
return new Error("Unknown error");
|
|
13
|
+
}, []);
|
|
9
14
|
const pay2 = useCallback(async (input) => {
|
|
10
15
|
setLoading(true);
|
|
11
16
|
try {
|
|
@@ -13,12 +18,13 @@ function useEscrowCheckout() {
|
|
|
13
18
|
setReady(true);
|
|
14
19
|
setError(null);
|
|
15
20
|
} catch (e) {
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
const normalized = normalizeError(e);
|
|
22
|
+
setError(normalized);
|
|
23
|
+
throw normalized;
|
|
18
24
|
} finally {
|
|
19
25
|
setLoading(false);
|
|
20
26
|
}
|
|
21
|
-
}, []);
|
|
27
|
+
}, [normalizeError]);
|
|
22
28
|
useEffect(() => {
|
|
23
29
|
if (typeof window === "undefined") {
|
|
24
30
|
setLoading(false);
|
|
@@ -41,28 +47,31 @@ function EscrowCheckoutButton({
|
|
|
41
47
|
title,
|
|
42
48
|
customerId
|
|
43
49
|
}) {
|
|
44
|
-
const {
|
|
50
|
+
const { loading, error, pay: pay2 } = useEscrowCheckout();
|
|
45
51
|
const handleClick = useCallback(async () => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
try {
|
|
53
|
+
await pay2({
|
|
54
|
+
paymentToken,
|
|
55
|
+
reference,
|
|
56
|
+
redirectUrl,
|
|
57
|
+
brand,
|
|
58
|
+
logoUrl,
|
|
59
|
+
callback,
|
|
60
|
+
onClose,
|
|
61
|
+
extra,
|
|
62
|
+
customerId
|
|
63
|
+
});
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
57
66
|
}, [pay2, paymentToken, reference, redirectUrl, brand, logoUrl, callback, onClose, extra, customerId]);
|
|
58
67
|
return /* @__PURE__ */ jsx(
|
|
59
68
|
"button",
|
|
60
69
|
{
|
|
61
70
|
type: "button",
|
|
62
71
|
onClick: handleClick,
|
|
63
|
-
disabled: disabled || loading
|
|
72
|
+
disabled: disabled || loading,
|
|
64
73
|
className,
|
|
65
|
-
"aria-disabled": disabled || loading
|
|
74
|
+
"aria-disabled": disabled || loading,
|
|
66
75
|
title: title ?? (error ? error.message : void 0),
|
|
67
76
|
children
|
|
68
77
|
}
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/useEscrowCheckout.ts","../../src/react/EscrowCheckoutButton.tsx"],"names":["pay","useCallback"],"mappings":";;;;AAcO,SAAS,iBAAA,GAA6C;AACzD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAS,KAAK,CAAA;AACxC,EAAA,MAAM,CAAC,
|
|
1
|
+
{"version":3,"sources":["../../src/react/useEscrowCheckout.ts","../../src/react/EscrowCheckoutButton.tsx"],"names":["pay","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react';\r\nimport { pay as corePay } from '../index';\r\n\r\nexport interface UseEscrowCheckoutResult {\r\n ready: boolean;\r\n loading: boolean;\r\n error: Error | null;\r\n pay: typeof corePay;\r\n}\r\n\r\n/**\r\n * React hook that preloads the widget script (implicitly via pay) and exposes pay(...)\r\n * Since the library abstracts script loading and session creation, the hook is thin.\r\n */\r\nexport function useEscrowCheckout(): UseEscrowCheckoutResult {\n const [ready, setReady] = useState(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const normalizeError = useCallback((err: unknown): Error => {\n if (err instanceof Error) return err;\n if (typeof err === 'string') return new Error(err);\n return new Error('Unknown error');\n }, []);\n\n // Lazy mark \"ready\" after first successful pay or script load attempt.\n // If you want to preload the script earlier, you may trigger a no-op pay with a dry-run endpoint on your side.\n\n // Provide a stable pay function that reports loading state.\n const pay = useCallback<typeof corePay>(async (input) => {\n setLoading(true);\r\n try {\r\n await corePay(input);\n setReady(true);\n setError(null);\n } catch (e) {\n const normalized = normalizeError(e);\n setError(normalized);\n throw normalized;\n } finally {\n setLoading(false);\n }\n }, [normalizeError]);\n\r\n // In SSR, mark not loading\n useEffect(() => {\n if (typeof window === 'undefined') {\n setLoading(false);\n }\n }, []);\n\r\n return useMemo(() => ({ ready, loading, error, pay }), [ready, loading, error, pay]);\r\n}\n","import React, { useCallback } from 'react';\r\nimport { useEscrowCheckout } from './useEscrowCheckout';\r\n\r\nexport interface EscrowCheckoutButtonProps {\r\n paymentToken: string;\r\n reference: string;\r\n redirectUrl: string;\r\n children?: React.ReactNode;\r\n disabled?: boolean;\r\n className?: string;\r\n title?: string;\r\n\r\n brand?: string;\r\n customerId?: string;\r\n logoUrl?: string;\r\n callback?: (result: any) => void;\r\n onClose?: () => void;\r\n extra?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Button that triggers pay(...) with your provided inputs.\r\n * The library handles both session creation and widget loading.\r\n */\r\nexport function EscrowCheckoutButton({\r\n paymentToken,\r\n reference,\r\n redirectUrl,\r\n brand,\r\n logoUrl,\r\n callback,\r\n onClose,\r\n extra,\r\n children = 'Pay',\r\n disabled,\r\n className,\r\n title,\r\n customerId\r\n }: EscrowCheckoutButtonProps) {\r\n const { loading, error, pay } = useEscrowCheckout();\n\n const handleClick = useCallback(async () => {\n try {\n await pay({\n paymentToken,\n reference,\n redirectUrl,\n brand,\n logoUrl,\n callback,\n onClose,\n extra,\n customerId\n });\n } catch {\n // Error state is already exposed via the hook.\n }\n }, [pay, paymentToken, reference, redirectUrl, brand, logoUrl, callback, onClose, extra, customerId]);\n\r\n return (\r\n <button\r\n type=\"button\"\n onClick={handleClick}\n disabled={disabled || loading}\n className={className}\n aria-disabled={disabled || loading}\n title={title ?? (error ? error.message : undefined)}\n >\n {children}\n </button>\n );\r\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payluk-escrow-inline-checkout",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "TypeScript escrow checkout wrapper with built-in script loader and session creation, ready for React/Next.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,12 +29,14 @@
|
|
|
29
29
|
"@types/react": "^18.3.5",
|
|
30
30
|
"rimraf": "^6.0.1",
|
|
31
31
|
"tsup": "^8.0.1",
|
|
32
|
-
"typescript": "^5.6.3"
|
|
32
|
+
"typescript": "^5.6.3",
|
|
33
|
+
"vitest": "^2.1.5"
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
36
|
"clean": "rimraf dist",
|
|
36
37
|
"build": "tsup",
|
|
38
|
+
"test": "vitest run",
|
|
37
39
|
"typecheck": "tsc --noEmit",
|
|
38
|
-
"prepublishOnly": "npm run clean && npm run build"
|
|
40
|
+
"prepublishOnly": "npm run clean && npm run test & npm run build"
|
|
39
41
|
}
|
|
40
|
-
}
|
|
42
|
+
}
|
package/dist/chunk-CQKTT6UR.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
var DEFAULT_SCRIPT_URL = "https://checkout.payluk.ng/escrow-checkout.min.js";
|
|
3
|
-
var DEFAULT_GLOBAL_NAME = "EscrowCheckout";
|
|
4
|
-
var CONFIG = null;
|
|
5
|
-
function initEscrowCheckout(config) {
|
|
6
|
-
if (!config?.publicKey) throw new Error('initEscrowCheckout: "publicKey" is required.');
|
|
7
|
-
CONFIG = {
|
|
8
|
-
publicKey: config.publicKey,
|
|
9
|
-
scriptUrl: config.scriptUrlOverride || DEFAULT_SCRIPT_URL,
|
|
10
|
-
globalName: config.globalName || DEFAULT_GLOBAL_NAME,
|
|
11
|
-
crossOrigin: config.crossOrigin ?? "anonymous"
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
function assertConfigured(config) {
|
|
15
|
-
if (!config) {
|
|
16
|
-
throw new Error("Escrow checkout not initialized. Call initEscrowCheckout({ apiBaseUrl, publicKey }) first.");
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
async function loadWidget() {
|
|
20
|
-
assertConfigured(CONFIG);
|
|
21
|
-
const { scriptUrl, globalName, crossOrigin } = CONFIG;
|
|
22
|
-
if (typeof window === "undefined") {
|
|
23
|
-
throw new Error("EscrowCheckout can only be loaded in the browser.");
|
|
24
|
-
}
|
|
25
|
-
const win = window;
|
|
26
|
-
win.__escrowCheckoutLoader ?? (win.__escrowCheckoutLoader = {});
|
|
27
|
-
if (win.__escrowCheckoutLoader[scriptUrl]) {
|
|
28
|
-
return win.__escrowCheckoutLoader[scriptUrl];
|
|
29
|
-
}
|
|
30
|
-
if (typeof win[globalName] === "function") {
|
|
31
|
-
const fn = win[globalName];
|
|
32
|
-
win.__escrowCheckoutLoader[scriptUrl] = Promise.resolve(fn);
|
|
33
|
-
return fn;
|
|
34
|
-
}
|
|
35
|
-
win.__escrowCheckoutLoader[scriptUrl] = new Promise((resolve, reject) => {
|
|
36
|
-
const script = document.createElement("script");
|
|
37
|
-
script.src = scriptUrl;
|
|
38
|
-
script.async = true;
|
|
39
|
-
if (crossOrigin) script.crossOrigin = crossOrigin;
|
|
40
|
-
script.onload = () => {
|
|
41
|
-
const fn = win[globalName];
|
|
42
|
-
if (typeof fn === "function") resolve(fn);
|
|
43
|
-
else reject(new Error(`Escrow checkout script loaded, but window.${globalName} is not a function.`));
|
|
44
|
-
};
|
|
45
|
-
script.onerror = () => reject(new Error(`Failed to load escrow checkout script: ${scriptUrl}`));
|
|
46
|
-
document.head.appendChild(script);
|
|
47
|
-
});
|
|
48
|
-
return win.__escrowCheckoutLoader[scriptUrl];
|
|
49
|
-
}
|
|
50
|
-
async function createSession(input) {
|
|
51
|
-
assertConfigured(CONFIG);
|
|
52
|
-
const { publicKey } = CONFIG;
|
|
53
|
-
const apiBaseUrl = publicKey.startsWith("pk_live_") ? "https://live.payluk.ng" : "https://staging.live.payluk.ng";
|
|
54
|
-
const resp = await fetch(`${apiBaseUrl}/v1/checkout/session`, {
|
|
55
|
-
method: "POST",
|
|
56
|
-
headers: { "Content-Type": "application/json" },
|
|
57
|
-
body: JSON.stringify({
|
|
58
|
-
paymentToken: input.paymentToken,
|
|
59
|
-
redirectUrl: input.redirectUrl,
|
|
60
|
-
reference: input.reference,
|
|
61
|
-
customerId: input.customerId,
|
|
62
|
-
publicKey
|
|
63
|
-
})
|
|
64
|
-
});
|
|
65
|
-
if (!resp.ok) {
|
|
66
|
-
const err = await resp.json().catch(() => ({}));
|
|
67
|
-
throw new Error(err.message || "Failed to create session");
|
|
68
|
-
}
|
|
69
|
-
return await resp.json();
|
|
70
|
-
}
|
|
71
|
-
async function pay(input) {
|
|
72
|
-
if (typeof window === "undefined") {
|
|
73
|
-
throw new Error("pay(...) can only run in the browser.");
|
|
74
|
-
}
|
|
75
|
-
const [{ session }, widget] = await Promise.all([createSession(input), loadWidget()]);
|
|
76
|
-
widget({
|
|
77
|
-
session,
|
|
78
|
-
logoUrl: input.logoUrl,
|
|
79
|
-
brand: input.brand,
|
|
80
|
-
callback: input.callback,
|
|
81
|
-
onClose: input.onClose,
|
|
82
|
-
customerId: input.customerId,
|
|
83
|
-
...input.extra ?? {}
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export { initEscrowCheckout, pay };
|
|
88
|
-
//# sourceMappingURL=chunk-CQKTT6UR.js.map
|
|
89
|
-
//# sourceMappingURL=chunk-CQKTT6UR.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"chunk-CQKTT6UR.js","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 ...(input.extra ?? {})\r\n });\r\n}"]}
|