@oleq-ai/pay 0.4.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oleq
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # @oleq-ai/pay
2
+
3
+ Browser SDK for [OleqPay](https://my.olefi.co) hosted checkout — modal iframe or full-page redirect.
4
+
5
+ Full documentation: [docs.oleq.co](https://docs.oleq.co).
6
+
7
+ Bugs & issues: [oleq.co/contact](https://oleq.co/contact).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @oleq-ai/pay
13
+ ```
14
+
15
+ ```bash
16
+ npm install @oleq-ai/pay
17
+ ```
18
+
19
+ ## Quick start
20
+
21
+ ```ts
22
+ import { OleqPayCheckout } from '@oleq-ai/pay';
23
+
24
+ const oleqpay = new OleqPayCheckout({
25
+ checkoutBaseUrl: 'https://my.olefi.co',
26
+ });
27
+
28
+ oleqpay.openIframe(
29
+ {
30
+ apikey: 'demo_public_key',
31
+ amount: '1500',
32
+ orderid: order.id,
33
+ reference: order.reference,
34
+ returnurl: location.href,
35
+ },
36
+ {
37
+ onSuccess: (e) => console.log('paid', e.vreference),
38
+ onError: (e) => console.log('failed', e.message),
39
+ onClose: () => console.log('closed'),
40
+ },
41
+ );
42
+ ```
43
+
44
+ Prefer minting the session URL on your server with `buildUrl()`, then hand that URL to `openIframe()` / `redirect()` in the browser.
45
+
46
+ ## Surface
47
+
48
+ | Entry | Exports |
49
+ | -------------------- | ----------------------------------------------------- |
50
+ | `@oleq-ai/pay` | `OleqPayCheckout`, `OleqPayCheckoutError`, types |
51
+ | `@oleq-ai/pay/react` | `useOleqPayCheckout()` |
52
+ | `@oleq-ai/pay/nest` | `OleqPayCheckoutModule.forRoot()` / `.forRootAsync()` |
53
+
54
+ ## License
55
+
56
+ MIT
@@ -0,0 +1,63 @@
1
+ interface CheckoutPayload {
2
+ apikey: string;
3
+ amount: string | number;
4
+ orderid: string | number;
5
+ reference: string;
6
+ callbackurl?: string;
7
+ returnurl?: string;
8
+ }
9
+ type CheckoutEvent = {
10
+ type: 'oleqpay:ready';
11
+ } | {
12
+ type: 'oleqpay:success';
13
+ vreference: string;
14
+ amount?: string;
15
+ } | {
16
+ type: 'oleqpay:failed';
17
+ vreference?: string;
18
+ message?: string;
19
+ } | {
20
+ type: 'oleqpay:close';
21
+ };
22
+ type OleqPayCheckoutOptions = {
23
+ checkoutBaseUrl?: string;
24
+ locale?: string;
25
+ };
26
+ interface OpenOptions {
27
+ onSuccess?: (event: Extract<CheckoutEvent, {
28
+ type: 'oleqpay:success';
29
+ }>) => void;
30
+ onError?: (event: Extract<CheckoutEvent, {
31
+ type: 'oleqpay:failed';
32
+ }>) => void;
33
+ onClose?: () => void;
34
+ }
35
+
36
+ declare class OleqPayCheckout {
37
+ private readonly checkoutBaseUrl;
38
+ private readonly locale;
39
+ private activeModal;
40
+ private messageHandler;
41
+ constructor(options?: OleqPayCheckoutOptions);
42
+ /**
43
+ * Build a hosted-checkout URL from a payload.
44
+ * Safe on the server (no DOM) — use this to mint session URLs in Nest/backends.
45
+ */
46
+ buildUrl(payload: CheckoutPayload): string;
47
+ /** Opens checkout in a modal iframe. Accepts either a raw payload or a pre-built session URL. */
48
+ openIframe(payloadOrUrl: CheckoutPayload | string, options?: OpenOptions): void;
49
+ /** Full-page redirect to hosted checkout — mirrors Stripe Checkout / Paystack redirect flow. */
50
+ redirect(payloadOrUrl: CheckoutPayload | string): void;
51
+ /** Programmatically close an open iframe modal. */
52
+ close(): void;
53
+ private resolveUrl;
54
+ private teardown;
55
+ }
56
+
57
+ type OleqPayCheckoutErrorCode = 'ssr_unsupported' | 'invalid_api_key' | 'invalid_payload';
58
+ declare class OleqPayCheckoutError extends Error {
59
+ readonly code: OleqPayCheckoutErrorCode;
60
+ constructor(message: string, code: OleqPayCheckoutErrorCode);
61
+ }
62
+
63
+ export { type CheckoutEvent, type CheckoutPayload, OleqPayCheckout, OleqPayCheckoutError, type OleqPayCheckoutErrorCode, type OleqPayCheckoutOptions, type OpenOptions, OleqPayCheckout as default };
@@ -0,0 +1,63 @@
1
+ interface CheckoutPayload {
2
+ apikey: string;
3
+ amount: string | number;
4
+ orderid: string | number;
5
+ reference: string;
6
+ callbackurl?: string;
7
+ returnurl?: string;
8
+ }
9
+ type CheckoutEvent = {
10
+ type: 'oleqpay:ready';
11
+ } | {
12
+ type: 'oleqpay:success';
13
+ vreference: string;
14
+ amount?: string;
15
+ } | {
16
+ type: 'oleqpay:failed';
17
+ vreference?: string;
18
+ message?: string;
19
+ } | {
20
+ type: 'oleqpay:close';
21
+ };
22
+ type OleqPayCheckoutOptions = {
23
+ checkoutBaseUrl?: string;
24
+ locale?: string;
25
+ };
26
+ interface OpenOptions {
27
+ onSuccess?: (event: Extract<CheckoutEvent, {
28
+ type: 'oleqpay:success';
29
+ }>) => void;
30
+ onError?: (event: Extract<CheckoutEvent, {
31
+ type: 'oleqpay:failed';
32
+ }>) => void;
33
+ onClose?: () => void;
34
+ }
35
+
36
+ declare class OleqPayCheckout {
37
+ private readonly checkoutBaseUrl;
38
+ private readonly locale;
39
+ private activeModal;
40
+ private messageHandler;
41
+ constructor(options?: OleqPayCheckoutOptions);
42
+ /**
43
+ * Build a hosted-checkout URL from a payload.
44
+ * Safe on the server (no DOM) — use this to mint session URLs in Nest/backends.
45
+ */
46
+ buildUrl(payload: CheckoutPayload): string;
47
+ /** Opens checkout in a modal iframe. Accepts either a raw payload or a pre-built session URL. */
48
+ openIframe(payloadOrUrl: CheckoutPayload | string, options?: OpenOptions): void;
49
+ /** Full-page redirect to hosted checkout — mirrors Stripe Checkout / Paystack redirect flow. */
50
+ redirect(payloadOrUrl: CheckoutPayload | string): void;
51
+ /** Programmatically close an open iframe modal. */
52
+ close(): void;
53
+ private resolveUrl;
54
+ private teardown;
55
+ }
56
+
57
+ type OleqPayCheckoutErrorCode = 'ssr_unsupported' | 'invalid_api_key' | 'invalid_payload';
58
+ declare class OleqPayCheckoutError extends Error {
59
+ readonly code: OleqPayCheckoutErrorCode;
60
+ constructor(message: string, code: OleqPayCheckoutErrorCode);
61
+ }
62
+
63
+ export { type CheckoutEvent, type CheckoutPayload, OleqPayCheckout, OleqPayCheckoutError, type OleqPayCheckoutErrorCode, type OleqPayCheckoutOptions, type OpenOptions, OleqPayCheckout as default };
package/dist/index.js ADDED
@@ -0,0 +1,298 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ OleqPayCheckout: () => OleqPayCheckout,
24
+ OleqPayCheckoutError: () => OleqPayCheckoutError,
25
+ default: () => OleqPayCheckout
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/encode.ts
30
+ function encodePaymentData(payload) {
31
+ const json = JSON.stringify(payload);
32
+ const firstEncode = utf8ToBase64(json);
33
+ return utf8ToBase64(firstEncode);
34
+ }
35
+ function utf8ToBase64(input) {
36
+ const bytes = new TextEncoder().encode(input);
37
+ let binary = "";
38
+ bytes.forEach((byte) => {
39
+ binary += String.fromCharCode(byte);
40
+ });
41
+ return btoa(binary);
42
+ }
43
+ function buildCheckoutUrl(baseUrl, payload, options) {
44
+ const qp = encodePaymentData(payload);
45
+ const locale = options?.locale ?? "en";
46
+ const path = options?.path ?? "p";
47
+ const normalizedBase = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
48
+ const url = new URL(`${locale}/${path}`, normalizedBase);
49
+ url.searchParams.set("qp", qp);
50
+ return url.toString();
51
+ }
52
+
53
+ // src/errors.ts
54
+ var OleqPayCheckoutError = class extends Error {
55
+ constructor(message, code) {
56
+ super(message);
57
+ this.name = "OleqPayCheckoutError";
58
+ this.code = code;
59
+ }
60
+ };
61
+
62
+ // src/modal.ts
63
+ var OVERLAY_ID = "oleqpay-checkout-overlay";
64
+ function openCheckoutModal(url, onCloseRequested) {
65
+ removeExistingModal();
66
+ const overlay = document.createElement("div");
67
+ overlay.id = OVERLAY_ID;
68
+ Object.assign(overlay.style, {
69
+ position: "fixed",
70
+ inset: "0",
71
+ zIndex: "2147483000",
72
+ display: "flex",
73
+ alignItems: "center",
74
+ justifyContent: "center",
75
+ background: "rgba(15, 23, 42, 0.55)",
76
+ backdropFilter: "blur(2px)"
77
+ });
78
+ const panel = document.createElement("div");
79
+ Object.assign(panel.style, {
80
+ position: "relative",
81
+ width: "min(440px, calc(100vw - 32px))",
82
+ height: "min(720px, calc(100vh - 32px))",
83
+ borderRadius: "16px",
84
+ overflow: "hidden",
85
+ boxShadow: "0 20px 60px rgba(0,0,0,0.35)",
86
+ background: "#fff"
87
+ });
88
+ const closeBtn = document.createElement("button");
89
+ closeBtn.setAttribute("aria-label", "Close checkout");
90
+ closeBtn.textContent = "\xD7";
91
+ Object.assign(closeBtn.style, {
92
+ position: "absolute",
93
+ top: "8px",
94
+ right: "8px",
95
+ width: "32px",
96
+ height: "32px",
97
+ borderRadius: "9999px",
98
+ border: "none",
99
+ background: "rgba(0,0,0,0.06)",
100
+ fontSize: "20px",
101
+ lineHeight: "1",
102
+ cursor: "pointer",
103
+ zIndex: "1"
104
+ });
105
+ closeBtn.onclick = () => onCloseRequested();
106
+ const iframe = document.createElement("iframe");
107
+ iframe.src = url;
108
+ iframe.title = "OleqPay Checkout";
109
+ iframe.setAttribute(
110
+ "allow",
111
+ "payment *; clipboard-write; camera; publickey-credentials-get"
112
+ );
113
+ Object.assign(iframe.style, {
114
+ width: "100%",
115
+ height: "100%",
116
+ border: "0"
117
+ });
118
+ panel.appendChild(closeBtn);
119
+ panel.appendChild(iframe);
120
+ overlay.appendChild(panel);
121
+ document.body.appendChild(overlay);
122
+ document.body.style.overflow = "hidden";
123
+ const onKeydown = (e) => {
124
+ if (e.key === "Escape") onCloseRequested();
125
+ };
126
+ window.addEventListener("keydown", onKeydown);
127
+ return {
128
+ iframe,
129
+ close: () => {
130
+ window.removeEventListener("keydown", onKeydown);
131
+ document.body.style.overflow = "";
132
+ overlay.remove();
133
+ }
134
+ };
135
+ }
136
+ function removeExistingModal() {
137
+ const existing = document.getElementById(OVERLAY_ID);
138
+ if (!existing) return;
139
+ existing.remove();
140
+ document.body.style.overflow = "";
141
+ }
142
+
143
+ // src/api-key.ts
144
+ function assertPublicApiKey(apikey) {
145
+ if (!apikey) {
146
+ throw new OleqPayCheckoutError(
147
+ "OleqPayCheckout: apikey is required.",
148
+ "invalid_api_key"
149
+ );
150
+ }
151
+ if (apikey.startsWith("sk_")) {
152
+ throw new OleqPayCheckoutError(
153
+ "OleqPayCheckout: this looks like a secret key (sk_\u2026). Only public keys belong in browser code \u2014 mint a session server-side with buildUrl() and pass the resulting URL to openIframe()/redirect() instead.",
154
+ "invalid_api_key"
155
+ );
156
+ }
157
+ }
158
+
159
+ // src/payload.ts
160
+ function assertValidPayload(payload) {
161
+ assertPublicApiKey(payload.apikey);
162
+ if (payload.apikey.length < 10) {
163
+ throw new OleqPayCheckoutError(
164
+ "OleqPayCheckout: apikey must be at least 10 characters.",
165
+ "invalid_api_key"
166
+ );
167
+ }
168
+ if (payload.orderid === void 0 || payload.orderid === "") {
169
+ throw new OleqPayCheckoutError(
170
+ "OleqPayCheckout: orderid is required \u2014 the checkout page rejects payloads without it.",
171
+ "invalid_payload"
172
+ );
173
+ }
174
+ if (!payload.reference) {
175
+ throw new OleqPayCheckoutError(
176
+ "OleqPayCheckout: reference is required \u2014 the checkout page rejects payloads without it.",
177
+ "invalid_payload"
178
+ );
179
+ }
180
+ const amountValue = typeof payload.amount === "number" ? payload.amount : parseFloat(payload.amount);
181
+ if (!Number.isFinite(amountValue) || amountValue <= 0) {
182
+ throw new OleqPayCheckoutError(
183
+ "OleqPayCheckout: amount must be a number greater than 0.",
184
+ "invalid_payload"
185
+ );
186
+ }
187
+ if (payload.callbackurl && !isValidUrl(payload.callbackurl)) {
188
+ throw new OleqPayCheckoutError(
189
+ "OleqPayCheckout: callbackurl must be a valid URL.",
190
+ "invalid_payload"
191
+ );
192
+ }
193
+ if (payload.returnurl && !isValidUrl(payload.returnurl)) {
194
+ throw new OleqPayCheckoutError(
195
+ "OleqPayCheckout: returnurl must be a valid URL.",
196
+ "invalid_payload"
197
+ );
198
+ }
199
+ }
200
+ function isValidUrl(value) {
201
+ try {
202
+ new URL(value);
203
+ return true;
204
+ } catch {
205
+ return false;
206
+ }
207
+ }
208
+
209
+ // src/types.ts
210
+ var DEFAULT_CHECKOUT_BASE_URL = "https://my.olefi.co";
211
+
212
+ // src/client.ts
213
+ var OleqPayCheckout = class {
214
+ constructor(options = {}) {
215
+ this.activeModal = null;
216
+ this.messageHandler = null;
217
+ this.checkoutBaseUrl = options.checkoutBaseUrl ?? DEFAULT_CHECKOUT_BASE_URL;
218
+ this.locale = options.locale ?? "en";
219
+ }
220
+ /**
221
+ * Build a hosted-checkout URL from a payload.
222
+ * Safe on the server (no DOM) — use this to mint session URLs in Nest/backends.
223
+ */
224
+ buildUrl(payload) {
225
+ assertValidPayload(payload);
226
+ return buildCheckoutUrl(this.checkoutBaseUrl, payload, {
227
+ locale: this.locale
228
+ });
229
+ }
230
+ /** Opens checkout in a modal iframe. Accepts either a raw payload or a pre-built session URL. */
231
+ openIframe(payloadOrUrl, options = {}) {
232
+ if (typeof window === "undefined") {
233
+ throw new OleqPayCheckoutError(
234
+ "OleqPayCheckout.openIframe() can only run in a browser.",
235
+ "ssr_unsupported"
236
+ );
237
+ }
238
+ this.close();
239
+ const url = this.resolveUrl(payloadOrUrl);
240
+ const targetOrigin = new URL(url).origin;
241
+ const onUserClose = () => {
242
+ this.close();
243
+ options.onClose?.();
244
+ };
245
+ this.activeModal = openCheckoutModal(url, onUserClose);
246
+ this.messageHandler = (event) => {
247
+ if (event.origin !== targetOrigin) return;
248
+ const data = event.data;
249
+ if (!data || typeof data !== "object" || !("type" in data)) return;
250
+ switch (data.type) {
251
+ case "oleqpay:success":
252
+ options.onSuccess?.(data);
253
+ this.close();
254
+ break;
255
+ case "oleqpay:failed":
256
+ options.onError?.(data);
257
+ break;
258
+ case "oleqpay:close":
259
+ onUserClose();
260
+ break;
261
+ default:
262
+ break;
263
+ }
264
+ };
265
+ window.addEventListener("message", this.messageHandler);
266
+ }
267
+ /** Full-page redirect to hosted checkout — mirrors Stripe Checkout / Paystack redirect flow. */
268
+ redirect(payloadOrUrl) {
269
+ if (typeof window === "undefined") {
270
+ throw new OleqPayCheckoutError(
271
+ "OleqPayCheckout.redirect() can only run in a browser.",
272
+ "ssr_unsupported"
273
+ );
274
+ }
275
+ window.location.href = this.resolveUrl(payloadOrUrl);
276
+ }
277
+ /** Programmatically close an open iframe modal. */
278
+ close() {
279
+ this.activeModal?.close();
280
+ this.teardown();
281
+ }
282
+ resolveUrl(payloadOrUrl) {
283
+ if (typeof payloadOrUrl === "string") return payloadOrUrl;
284
+ return this.buildUrl(payloadOrUrl);
285
+ }
286
+ teardown() {
287
+ if (this.messageHandler) {
288
+ window.removeEventListener("message", this.messageHandler);
289
+ this.messageHandler = null;
290
+ }
291
+ this.activeModal = null;
292
+ }
293
+ };
294
+ // Annotate the CommonJS export names for ESM import in node:
295
+ 0 && (module.exports = {
296
+ OleqPayCheckout,
297
+ OleqPayCheckoutError
298
+ });