@vittascore/payment-widget-react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # @vittascore/payment-widget-react
2
+
3
+ Official Vittas payment widget for React. Fully typed with TypeScript.
4
+
5
+ > **Naming convention** — framework-specific packages follow the pattern `@vittascore/payment-widget-{framework}`.
6
+ > Other packages in this family: `@vittascore/payment-widget-vue` (planned), `@vittascore/payment-widget-angular` (planned).
7
+
8
+ For contributing, local linking, and publishing instructions see [CONTRIBUTING.md](./CONTRIBUTING.md).
9
+
10
+ ---
11
+
12
+ ## How it works
13
+
14
+ ```
15
+ Your Backend ──POST /api/widget/sessions──► Vittas API
16
+ ◄── { clientSecret: "cs_..." } ──
17
+
18
+ Your Frontend ──clientSecret──► PaymentWidget ──loads widget.js──► Vittas CDN
19
+ ```
20
+
21
+ 1. Your backend creates a payment session (amount locked server-side) and receives a `clientSecret`.
22
+ 2. Your frontend receives the `clientSecret` and passes it to this package.
23
+ 3. The package loads the Vittas CDN `widget.js` script and calls `VittasPay.init({ clientSecret })`.
24
+ 4. The widget handles everything — UI, polling, success/failure — then fires your callbacks.
25
+
26
+ The amount and currency are **locked server-side** and cannot be tampered with by the browser.
27
+
28
+ ---
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ npm install @vittascore/payment-widget-react
34
+ # or
35
+ yarn add @vittascore/payment-widget-react
36
+ # or
37
+ pnpm add @vittascore/payment-widget-react
38
+ ```
39
+
40
+ React 17+ and react-dom are peer dependencies — install them alongside the package if you haven't already.
41
+
42
+ ---
43
+
44
+ ## Step 1 — Create a session on your backend
45
+
46
+ Your server calls the Vittas API with your **secret key** to create a payment session.
47
+ The response includes a `clientSecret` (`cs_...`) that you forward to the frontend.
48
+
49
+ **Endpoint:** `POST {api_base}/api/widget/sessions`
50
+ **Auth:** `Authorization: Bearer sk_test_…` (your secret key, never exposed to the browser)
51
+
52
+ ```http
53
+ POST https://dev-api.core.vittasinternational.com/api/widget/sessions
54
+ Authorization: Bearer sk_test_xxxxxxxxxxxx
55
+ Content-Type: application/json
56
+
57
+ {
58
+ "amount": 500000,
59
+ "currency": "NGN",
60
+ "reference": "order_abc123",
61
+ "payerName": "Ada Lovelace",
62
+ "payerEmail": "ada@example.com",
63
+ "payerPhone": "+2348012345678",
64
+ "metadata": { "orderId": "abc123" }
65
+ }
66
+ ```
67
+
68
+ | Field | Type | Required | Description |
69
+ |-------|------|----------|-------------|
70
+ | `amount` | `number` | ✅ | Amount in kobo (₦1 = 100 kobo) — minimum 100 |
71
+ | `currency` | `string` | ✅ | ISO 4217 code, e.g. `'NGN'` |
72
+ | `reference` | `string` | — | Your idempotency key — auto-generated if omitted |
73
+ | `payerName` | `string` | — | Pre-fills the customer name |
74
+ | `payerEmail` | `string` | — | Pre-fills the customer email |
75
+ | `payerPhone` | `string` | — | Pre-fills the customer phone |
76
+ | `metadata` | `object` | — | Arbitrary data echoed back in webhooks |
77
+
78
+ The response contains a `clientSecret` (`cs_...`). Pass that to your frontend.
79
+
80
+ ---
81
+
82
+ ## Step 2 — Open the widget on the frontend
83
+
84
+ Pass the `clientSecret` your backend returned directly to the component or hook.
85
+
86
+ ### Component
87
+
88
+ ```tsx
89
+ import { PaymentWidget } from '@vittascore/payment-widget-react';
90
+
91
+ // clientSecret comes from your backend — e.g. server-side props, an API route, etc.
92
+ export default function CheckoutPage({ clientSecret }: { clientSecret: string }) {
93
+ return (
94
+ <PaymentWidget
95
+ mode="TEST"
96
+ clientSecret={clientSecret}
97
+ onSuccess={(ref) => console.log('paid', ref)}
98
+ onCancel={() => console.log('cancelled')}
99
+ onError={(err) => console.error(err.message)}
100
+ />
101
+ );
102
+ }
103
+ ```
104
+
105
+ ### Hook (programmatic)
106
+
107
+ ```tsx
108
+ import { useVittasPayment } from '@vittascore/payment-widget-react';
109
+
110
+ export default function CheckoutPage({ clientSecret }: { clientSecret: string }) {
111
+ const { open, isOpen } = useVittasPayment({
112
+ mode: 'TEST',
113
+ clientSecret,
114
+ onSuccess: (ref) => console.log('paid', ref),
115
+ onCancel: () => console.log('cancelled'),
116
+ });
117
+
118
+ return (
119
+ <button onClick={open} disabled={isOpen}>
120
+ {isOpen ? 'Opening…' : 'Pay Now'}
121
+ </button>
122
+ );
123
+ }
124
+ ```
125
+
126
+ ### Custom trigger
127
+
128
+ Pass any element as `children` and it becomes the click target:
129
+
130
+ ```tsx
131
+ <PaymentWidget mode="LIVE" clientSecret={clientSecret}>
132
+ <img src="/pay-button.svg" alt="Pay with Vittas" />
133
+ </PaymentWidget>
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Props / options
139
+
140
+ | Prop | Type | Required | Description |
141
+ |------|------|----------|-------------|
142
+ | `mode` | `'TEST' \| 'LIVE'` | ✅ | Determines which CDN and API environment to use |
143
+ | `clientSecret` | `string` | ✅ | Session client secret from your backend (`cs_...`) |
144
+ | `onSuccess` | `(ref: PaymentReference) => void \| Promise<void>` | — | Fires when the payment completes |
145
+ | `onError` | `(err: VittasPayError) => void \| Promise<void>` | — | Fires on payment failure |
146
+ | `onCancel` | `() => void` | — | Fires when the user closes the widget |
147
+ | `children` | `ReactNode` | — | Custom trigger element (component only) |
148
+ | `disabled` | `boolean` | — | Disables the trigger (component only) |
149
+
150
+ ---
151
+
152
+ ## Environments
153
+
154
+ | Mode | CDN | API |
155
+ |------|-----|-----|
156
+ | `TEST` | `https://dev-cdn.core.vittasinternational.com` | `https://dev-api.core.vittasinternational.com` |
157
+ | `LIVE` | `https://cdn.core.vittasinternational.com` | `https://api.core.vittasinternational.com` |
158
+
159
+ Widget script loaded at runtime: `{cdn}/latest/widget.js`
160
+
161
+ ---
162
+
163
+ ## TypeScript
164
+
165
+ All types are exported from the package root:
166
+
167
+ ```ts
168
+ import type {
169
+ WidgetMode,
170
+ VittasPaymentConfig,
171
+ PaymentWidgetProps,
172
+ PaymentReference,
173
+ VittasPayError,
174
+ } from '@vittascore/payment-widget-react';
175
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,157 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/hooks/useVittasPayment.ts
7
+
8
+ // src/constants.ts
9
+ var API_BASE_URLS = {
10
+ TEST: "https://dev-api.core.vittasinternational.com",
11
+ LIVE: "https://api.core.vittasinternational.com"
12
+ };
13
+ var CDN_BASE_URLS = {
14
+ TEST: "https://dev-cdn.core.vittasinternational.com",
15
+ LIVE: "https://cdn.core.vittasinternational.com"
16
+ };
17
+ var CDN_SCRIPT_URLS = {
18
+ TEST: "https://dev-cdn.core.vittasinternational.com/latest/widget.js",
19
+ LIVE: "https://cdn.core.vittasinternational.com/latest/widget.js"
20
+ };
21
+
22
+ // src/utils/loadWidgetScript.ts
23
+ var pending = /* @__PURE__ */ new Map();
24
+ function loadWidgetScript(mode) {
25
+ const src = CDN_SCRIPT_URLS[mode];
26
+ if (document.querySelector(`script[src="${src}"]`)) {
27
+ return Promise.resolve();
28
+ }
29
+ const existing = pending.get(src);
30
+ if (existing !== void 0) return existing;
31
+ const promise = new Promise((resolve, reject) => {
32
+ const script = document.createElement("script");
33
+ script.src = src;
34
+ script.async = true;
35
+ script.onload = () => {
36
+ pending.delete(src);
37
+ resolve();
38
+ };
39
+ script.onerror = () => {
40
+ pending.delete(src);
41
+ script.remove();
42
+ reject(new Error(`Failed to load Vittas widget script: ${src}`));
43
+ };
44
+ document.head.appendChild(script);
45
+ });
46
+ pending.set(src, promise);
47
+ return promise;
48
+ }
49
+
50
+ // src/hooks/useVittasPayment.ts
51
+ function useVittasPayment(options) {
52
+ const [isOpen, setIsOpen] = react.useState(false);
53
+ const optionsRef = react.useRef(options);
54
+ optionsRef.current = options;
55
+ const open = react.useCallback(() => {
56
+ const config = optionsRef.current;
57
+ setIsOpen(true);
58
+ loadWidgetScript(config.mode).then(() => {
59
+ var _a;
60
+ if (typeof ((_a = window.VittasPay) == null ? void 0 : _a.init) !== "function") {
61
+ throw new Error("window.VittasPay.init is not available after script load.");
62
+ }
63
+ window.VittasPay.init({
64
+ clientSecret: config.clientSecret,
65
+ onSuccess: (reference) => {
66
+ var _a2;
67
+ setIsOpen(false);
68
+ void ((_a2 = config.onSuccess) == null ? void 0 : _a2.call(config, reference));
69
+ },
70
+ onError: (err) => {
71
+ var _a2;
72
+ setIsOpen(false);
73
+ void ((_a2 = config.onError) == null ? void 0 : _a2.call(config, err));
74
+ },
75
+ onCancel: () => {
76
+ var _a2;
77
+ setIsOpen(false);
78
+ (_a2 = config.onCancel) == null ? void 0 : _a2.call(config);
79
+ }
80
+ });
81
+ }).catch((err) => {
82
+ var _a;
83
+ setIsOpen(false);
84
+ void ((_a = config.onError) == null ? void 0 : _a.call(config, {
85
+ message: err instanceof Error ? err.message : "Failed to load payment widget.",
86
+ status: "failed"
87
+ }));
88
+ });
89
+ }, []);
90
+ return { open, isOpen };
91
+ }
92
+ function PaymentWidget({
93
+ mode,
94
+ clientSecret,
95
+ onSuccess,
96
+ onError,
97
+ onCancel,
98
+ children,
99
+ className,
100
+ style,
101
+ disabled = false
102
+ }) {
103
+ const { open } = useVittasPayment({
104
+ mode,
105
+ clientSecret,
106
+ ...onSuccess !== void 0 && { onSuccess },
107
+ ...onError !== void 0 && { onError },
108
+ ...onCancel !== void 0 && { onCancel }
109
+ });
110
+ if (children) {
111
+ return /* @__PURE__ */ jsxRuntime.jsx(
112
+ "span",
113
+ {
114
+ onClick: disabled ? void 0 : open,
115
+ style: { display: "contents", cursor: disabled ? "not-allowed" : "pointer", ...style },
116
+ className,
117
+ children
118
+ }
119
+ );
120
+ }
121
+ return /* @__PURE__ */ jsxRuntime.jsx(
122
+ "button",
123
+ {
124
+ type: "button",
125
+ onClick: open,
126
+ disabled,
127
+ className,
128
+ style: {
129
+ display: "inline-flex",
130
+ alignItems: "center",
131
+ justifyContent: "center",
132
+ gap: "8px",
133
+ padding: "12px 24px",
134
+ background: disabled ? "#94a3b8" : "#1a56db",
135
+ color: "#ffffff",
136
+ fontSize: "15px",
137
+ fontWeight: 600,
138
+ lineHeight: 1,
139
+ border: "none",
140
+ borderRadius: "8px",
141
+ cursor: disabled ? "not-allowed" : "pointer",
142
+ userSelect: "none",
143
+ transition: "background 150ms ease",
144
+ ...style
145
+ },
146
+ children: "Pay Now"
147
+ }
148
+ );
149
+ }
150
+
151
+ exports.API_BASE_URLS = API_BASE_URLS;
152
+ exports.CDN_BASE_URLS = CDN_BASE_URLS;
153
+ exports.CDN_SCRIPT_URLS = CDN_SCRIPT_URLS;
154
+ exports.PaymentWidget = PaymentWidget;
155
+ exports.useVittasPayment = useVittasPayment;
156
+ //# sourceMappingURL=index.cjs.map
157
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/utils/loadWidgetScript.ts","../src/hooks/useVittasPayment.ts","../src/components/PaymentWidget.tsx"],"names":["useState","useRef","useCallback","_a","jsx"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { WidgetMode } from './types.js';\n\n/** REST API base URL — used for payment session requests. */\nexport const API_BASE_URLS: Readonly<Record<WidgetMode, string>> = {\n TEST: 'https://dev-api.core.vittasinternational.com',\n LIVE: 'https://api.core.vittasinternational.com',\n} as const;\n\n/** CDN base URL — hosts the widget script and the payment iframe page. */\nexport const CDN_BASE_URLS: Readonly<Record<WidgetMode, string>> = {\n TEST: 'https://dev-cdn.core.vittasinternational.com',\n LIVE: 'https://cdn.core.vittasinternational.com',\n} as const;\n\n/** Full URL to the hosted widget loader script. */\nexport const CDN_SCRIPT_URLS: Readonly<Record<WidgetMode, string>> = {\n TEST: 'https://dev-cdn.core.vittasinternational.com/latest/widget.js',\n LIVE: 'https://cdn.core.vittasinternational.com/latest/widget.js',\n} as const;\n\n/**\n * Expected postMessage origin when the payment iframe sends results back.\n * Must match the origin of the page loaded in the iframe (CDN-hosted frame).\n */\nexport const WIDGET_ORIGIN: Readonly<Record<WidgetMode, string>> = {\n TEST: 'https://dev-cdn.core.vittasinternational.com',\n LIVE: 'https://cdn.core.vittasinternational.com',\n} as const satisfies Readonly<Record<WidgetMode, string>>;\n","import { CDN_SCRIPT_URLS } from '../constants.js';\nimport type { WidgetMode } from '../types.js';\n\nconst pending = new Map<string, Promise<void>>();\n\n/**\n * Dynamically loads the Vittas CDN widget.js script once per mode.\n * Subsequent calls for the same mode return the cached promise.\n */\nexport function loadWidgetScript(mode: WidgetMode): Promise<void> {\n const src = CDN_SCRIPT_URLS[mode];\n\n // Already injected into the document\n if (document.querySelector(`script[src=\"${src}\"]`)) {\n return Promise.resolve();\n }\n\n // In-flight load — reuse the same promise\n const existing = pending.get(src);\n if (existing !== undefined) return existing;\n\n const promise = new Promise<void>((resolve, reject) => {\n const script = document.createElement('script');\n script.src = src;\n script.async = true;\n script.onload = () => {\n pending.delete(src);\n resolve();\n };\n script.onerror = () => {\n pending.delete(src);\n script.remove();\n reject(new Error(`Failed to load Vittas widget script: ${src}`));\n };\n document.head.appendChild(script);\n });\n\n pending.set(src, promise);\n return promise;\n}\n","import { useCallback, useRef, useState } from 'react';\nimport type { UseVittasPaymentOptions, UseVittasPaymentReturn } from '../types.js';\nimport { loadWidgetScript } from '../utils/loadWidgetScript.js';\n\nexport function useVittasPayment(options: UseVittasPaymentOptions): UseVittasPaymentReturn {\n const [isOpen, setIsOpen] = useState(false);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const open = useCallback(() => {\n const config = optionsRef.current;\n\n setIsOpen(true);\n\n loadWidgetScript(config.mode)\n .then(() => {\n if (typeof window.VittasPay?.init !== 'function') {\n throw new Error('window.VittasPay.init is not available after script load.');\n }\n\n window.VittasPay.init({\n clientSecret: config.clientSecret,\n onSuccess: (reference) => {\n setIsOpen(false);\n void config.onSuccess?.(reference);\n },\n onError: (err) => {\n setIsOpen(false);\n void config.onError?.(err);\n },\n onCancel: () => {\n setIsOpen(false);\n config.onCancel?.();\n },\n });\n })\n .catch((err: unknown) => {\n setIsOpen(false);\n void config.onError?.({\n message: err instanceof Error ? err.message : 'Failed to load payment widget.',\n status: 'failed',\n });\n });\n }, []);\n\n return { open, isOpen };\n}\n","import type { PaymentWidgetProps } from '../types.js';\nimport { useVittasPayment } from '../hooks/useVittasPayment.js';\n\nexport function PaymentWidget({\n mode,\n clientSecret,\n onSuccess,\n onError,\n onCancel,\n children,\n className,\n style,\n disabled = false,\n}: PaymentWidgetProps) {\n const { open } = useVittasPayment({\n mode,\n clientSecret,\n ...(onSuccess !== undefined && { onSuccess }),\n ...(onError !== undefined && { onError }),\n ...(onCancel !== undefined && { onCancel }),\n });\n\n if (children) {\n return (\n <span\n onClick={disabled ? undefined : open}\n style={{ display: 'contents', cursor: disabled ? 'not-allowed' : 'pointer', ...style }}\n className={className}\n >\n {children}\n </span>\n );\n }\n\n return (\n <button\n type=\"button\"\n onClick={open}\n disabled={disabled}\n className={className}\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '8px',\n padding: '12px 24px',\n background: disabled ? '#94a3b8' : '#1a56db',\n color: '#ffffff',\n fontSize: '15px',\n fontWeight: 600,\n lineHeight: 1,\n border: 'none',\n borderRadius: '8px',\n cursor: disabled ? 'not-allowed' : 'pointer',\n userSelect: 'none',\n transition: 'background 150ms ease',\n ...style,\n }}\n >\n Pay Now\n </button>\n );\n}\n"]}
@@ -0,0 +1,75 @@
1
+ import * as react from 'react';
2
+ import { CSSProperties } from 'react';
3
+
4
+ type WidgetMode = 'TEST' | 'LIVE';
5
+ interface PaymentReference {
6
+ id: string;
7
+ reference: string;
8
+ status: string;
9
+ }
10
+ interface VittasPayError {
11
+ message: string;
12
+ status: 'failed';
13
+ }
14
+ interface VittasPaymentConfig {
15
+ /**
16
+ * 'TEST' loads the widget from the dev CDN; 'LIVE' from production CDN.
17
+ */
18
+ mode: WidgetMode;
19
+ /**
20
+ * Session client secret obtained from your backend (cs_...).
21
+ * Never hard-code this — request it from your server right before opening
22
+ * the widget, then pass it here.
23
+ */
24
+ clientSecret: string;
25
+ }
26
+ interface PaymentWidgetProps extends VittasPaymentConfig {
27
+ /** Fires when the payment completes successfully. */
28
+ onSuccess?: (reference: PaymentReference) => void | Promise<void>;
29
+ /** Fires when a payment error occurs. */
30
+ onError?: (err: VittasPayError) => void | Promise<void>;
31
+ /** Fires when the user closes the widget before completing. */
32
+ onCancel?: () => void;
33
+ /** Pass a custom trigger element. Defaults to a styled "Pay Now" button. */
34
+ children?: React.ReactNode;
35
+ className?: string;
36
+ style?: CSSProperties;
37
+ /** Disables the trigger button. */
38
+ disabled?: boolean;
39
+ }
40
+ interface UseVittasPaymentOptions extends VittasPaymentConfig {
41
+ onSuccess?: (reference: PaymentReference) => void | Promise<void>;
42
+ onError?: (err: VittasPayError) => void | Promise<void>;
43
+ onCancel?: () => void;
44
+ }
45
+ interface UseVittasPaymentReturn {
46
+ open: () => void;
47
+ isOpen: boolean;
48
+ }
49
+ interface VittasWidgetConfig {
50
+ clientSecret: string;
51
+ onSuccess?: (reference: PaymentReference) => void | Promise<void>;
52
+ onError?: (err: VittasPayError) => void | Promise<void>;
53
+ onCancel?: () => void;
54
+ }
55
+ interface VittasPayGlobal {
56
+ init: (config: VittasWidgetConfig) => unknown;
57
+ }
58
+ declare global {
59
+ interface Window {
60
+ VittasPay?: VittasPayGlobal;
61
+ }
62
+ }
63
+
64
+ declare function PaymentWidget({ mode, clientSecret, onSuccess, onError, onCancel, children, className, style, disabled, }: PaymentWidgetProps): react.JSX.Element;
65
+
66
+ declare function useVittasPayment(options: UseVittasPaymentOptions): UseVittasPaymentReturn;
67
+
68
+ /** REST API base URL — used for payment session requests. */
69
+ declare const API_BASE_URLS: Readonly<Record<WidgetMode, string>>;
70
+ /** CDN base URL — hosts the widget script and the payment iframe page. */
71
+ declare const CDN_BASE_URLS: Readonly<Record<WidgetMode, string>>;
72
+ /** Full URL to the hosted widget loader script. */
73
+ declare const CDN_SCRIPT_URLS: Readonly<Record<WidgetMode, string>>;
74
+
75
+ export { API_BASE_URLS, CDN_BASE_URLS, CDN_SCRIPT_URLS, type PaymentReference, PaymentWidget, type PaymentWidgetProps, type UseVittasPaymentOptions, type UseVittasPaymentReturn, type VittasPayError, type VittasPaymentConfig, type WidgetMode, useVittasPayment };
@@ -0,0 +1,75 @@
1
+ import * as react from 'react';
2
+ import { CSSProperties } from 'react';
3
+
4
+ type WidgetMode = 'TEST' | 'LIVE';
5
+ interface PaymentReference {
6
+ id: string;
7
+ reference: string;
8
+ status: string;
9
+ }
10
+ interface VittasPayError {
11
+ message: string;
12
+ status: 'failed';
13
+ }
14
+ interface VittasPaymentConfig {
15
+ /**
16
+ * 'TEST' loads the widget from the dev CDN; 'LIVE' from production CDN.
17
+ */
18
+ mode: WidgetMode;
19
+ /**
20
+ * Session client secret obtained from your backend (cs_...).
21
+ * Never hard-code this — request it from your server right before opening
22
+ * the widget, then pass it here.
23
+ */
24
+ clientSecret: string;
25
+ }
26
+ interface PaymentWidgetProps extends VittasPaymentConfig {
27
+ /** Fires when the payment completes successfully. */
28
+ onSuccess?: (reference: PaymentReference) => void | Promise<void>;
29
+ /** Fires when a payment error occurs. */
30
+ onError?: (err: VittasPayError) => void | Promise<void>;
31
+ /** Fires when the user closes the widget before completing. */
32
+ onCancel?: () => void;
33
+ /** Pass a custom trigger element. Defaults to a styled "Pay Now" button. */
34
+ children?: React.ReactNode;
35
+ className?: string;
36
+ style?: CSSProperties;
37
+ /** Disables the trigger button. */
38
+ disabled?: boolean;
39
+ }
40
+ interface UseVittasPaymentOptions extends VittasPaymentConfig {
41
+ onSuccess?: (reference: PaymentReference) => void | Promise<void>;
42
+ onError?: (err: VittasPayError) => void | Promise<void>;
43
+ onCancel?: () => void;
44
+ }
45
+ interface UseVittasPaymentReturn {
46
+ open: () => void;
47
+ isOpen: boolean;
48
+ }
49
+ interface VittasWidgetConfig {
50
+ clientSecret: string;
51
+ onSuccess?: (reference: PaymentReference) => void | Promise<void>;
52
+ onError?: (err: VittasPayError) => void | Promise<void>;
53
+ onCancel?: () => void;
54
+ }
55
+ interface VittasPayGlobal {
56
+ init: (config: VittasWidgetConfig) => unknown;
57
+ }
58
+ declare global {
59
+ interface Window {
60
+ VittasPay?: VittasPayGlobal;
61
+ }
62
+ }
63
+
64
+ declare function PaymentWidget({ mode, clientSecret, onSuccess, onError, onCancel, children, className, style, disabled, }: PaymentWidgetProps): react.JSX.Element;
65
+
66
+ declare function useVittasPayment(options: UseVittasPaymentOptions): UseVittasPaymentReturn;
67
+
68
+ /** REST API base URL — used for payment session requests. */
69
+ declare const API_BASE_URLS: Readonly<Record<WidgetMode, string>>;
70
+ /** CDN base URL — hosts the widget script and the payment iframe page. */
71
+ declare const CDN_BASE_URLS: Readonly<Record<WidgetMode, string>>;
72
+ /** Full URL to the hosted widget loader script. */
73
+ declare const CDN_SCRIPT_URLS: Readonly<Record<WidgetMode, string>>;
74
+
75
+ export { API_BASE_URLS, CDN_BASE_URLS, CDN_SCRIPT_URLS, type PaymentReference, PaymentWidget, type PaymentWidgetProps, type UseVittasPaymentOptions, type UseVittasPaymentReturn, type VittasPayError, type VittasPaymentConfig, type WidgetMode, useVittasPayment };
package/dist/index.js ADDED
@@ -0,0 +1,151 @@
1
+ import { useState, useRef, useCallback } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ // src/hooks/useVittasPayment.ts
5
+
6
+ // src/constants.ts
7
+ var API_BASE_URLS = {
8
+ TEST: "https://dev-api.core.vittasinternational.com",
9
+ LIVE: "https://api.core.vittasinternational.com"
10
+ };
11
+ var CDN_BASE_URLS = {
12
+ TEST: "https://dev-cdn.core.vittasinternational.com",
13
+ LIVE: "https://cdn.core.vittasinternational.com"
14
+ };
15
+ var CDN_SCRIPT_URLS = {
16
+ TEST: "https://dev-cdn.core.vittasinternational.com/latest/widget.js",
17
+ LIVE: "https://cdn.core.vittasinternational.com/latest/widget.js"
18
+ };
19
+
20
+ // src/utils/loadWidgetScript.ts
21
+ var pending = /* @__PURE__ */ new Map();
22
+ function loadWidgetScript(mode) {
23
+ const src = CDN_SCRIPT_URLS[mode];
24
+ if (document.querySelector(`script[src="${src}"]`)) {
25
+ return Promise.resolve();
26
+ }
27
+ const existing = pending.get(src);
28
+ if (existing !== void 0) return existing;
29
+ const promise = new Promise((resolve, reject) => {
30
+ const script = document.createElement("script");
31
+ script.src = src;
32
+ script.async = true;
33
+ script.onload = () => {
34
+ pending.delete(src);
35
+ resolve();
36
+ };
37
+ script.onerror = () => {
38
+ pending.delete(src);
39
+ script.remove();
40
+ reject(new Error(`Failed to load Vittas widget script: ${src}`));
41
+ };
42
+ document.head.appendChild(script);
43
+ });
44
+ pending.set(src, promise);
45
+ return promise;
46
+ }
47
+
48
+ // src/hooks/useVittasPayment.ts
49
+ function useVittasPayment(options) {
50
+ const [isOpen, setIsOpen] = useState(false);
51
+ const optionsRef = useRef(options);
52
+ optionsRef.current = options;
53
+ const open = useCallback(() => {
54
+ const config = optionsRef.current;
55
+ setIsOpen(true);
56
+ loadWidgetScript(config.mode).then(() => {
57
+ var _a;
58
+ if (typeof ((_a = window.VittasPay) == null ? void 0 : _a.init) !== "function") {
59
+ throw new Error("window.VittasPay.init is not available after script load.");
60
+ }
61
+ window.VittasPay.init({
62
+ clientSecret: config.clientSecret,
63
+ onSuccess: (reference) => {
64
+ var _a2;
65
+ setIsOpen(false);
66
+ void ((_a2 = config.onSuccess) == null ? void 0 : _a2.call(config, reference));
67
+ },
68
+ onError: (err) => {
69
+ var _a2;
70
+ setIsOpen(false);
71
+ void ((_a2 = config.onError) == null ? void 0 : _a2.call(config, err));
72
+ },
73
+ onCancel: () => {
74
+ var _a2;
75
+ setIsOpen(false);
76
+ (_a2 = config.onCancel) == null ? void 0 : _a2.call(config);
77
+ }
78
+ });
79
+ }).catch((err) => {
80
+ var _a;
81
+ setIsOpen(false);
82
+ void ((_a = config.onError) == null ? void 0 : _a.call(config, {
83
+ message: err instanceof Error ? err.message : "Failed to load payment widget.",
84
+ status: "failed"
85
+ }));
86
+ });
87
+ }, []);
88
+ return { open, isOpen };
89
+ }
90
+ function PaymentWidget({
91
+ mode,
92
+ clientSecret,
93
+ onSuccess,
94
+ onError,
95
+ onCancel,
96
+ children,
97
+ className,
98
+ style,
99
+ disabled = false
100
+ }) {
101
+ const { open } = useVittasPayment({
102
+ mode,
103
+ clientSecret,
104
+ ...onSuccess !== void 0 && { onSuccess },
105
+ ...onError !== void 0 && { onError },
106
+ ...onCancel !== void 0 && { onCancel }
107
+ });
108
+ if (children) {
109
+ return /* @__PURE__ */ jsx(
110
+ "span",
111
+ {
112
+ onClick: disabled ? void 0 : open,
113
+ style: { display: "contents", cursor: disabled ? "not-allowed" : "pointer", ...style },
114
+ className,
115
+ children
116
+ }
117
+ );
118
+ }
119
+ return /* @__PURE__ */ jsx(
120
+ "button",
121
+ {
122
+ type: "button",
123
+ onClick: open,
124
+ disabled,
125
+ className,
126
+ style: {
127
+ display: "inline-flex",
128
+ alignItems: "center",
129
+ justifyContent: "center",
130
+ gap: "8px",
131
+ padding: "12px 24px",
132
+ background: disabled ? "#94a3b8" : "#1a56db",
133
+ color: "#ffffff",
134
+ fontSize: "15px",
135
+ fontWeight: 600,
136
+ lineHeight: 1,
137
+ border: "none",
138
+ borderRadius: "8px",
139
+ cursor: disabled ? "not-allowed" : "pointer",
140
+ userSelect: "none",
141
+ transition: "background 150ms ease",
142
+ ...style
143
+ },
144
+ children: "Pay Now"
145
+ }
146
+ );
147
+ }
148
+
149
+ export { API_BASE_URLS, CDN_BASE_URLS, CDN_SCRIPT_URLS, PaymentWidget, useVittasPayment };
150
+ //# sourceMappingURL=index.js.map
151
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/utils/loadWidgetScript.ts","../src/hooks/useVittasPayment.ts","../src/components/PaymentWidget.tsx"],"names":["_a"],"mappings":"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","file":"index.js","sourcesContent":["import type { WidgetMode } from './types.js';\n\n/** REST API base URL — used for payment session requests. */\nexport const API_BASE_URLS: Readonly<Record<WidgetMode, string>> = {\n TEST: 'https://dev-api.core.vittasinternational.com',\n LIVE: 'https://api.core.vittasinternational.com',\n} as const;\n\n/** CDN base URL — hosts the widget script and the payment iframe page. */\nexport const CDN_BASE_URLS: Readonly<Record<WidgetMode, string>> = {\n TEST: 'https://dev-cdn.core.vittasinternational.com',\n LIVE: 'https://cdn.core.vittasinternational.com',\n} as const;\n\n/** Full URL to the hosted widget loader script. */\nexport const CDN_SCRIPT_URLS: Readonly<Record<WidgetMode, string>> = {\n TEST: 'https://dev-cdn.core.vittasinternational.com/latest/widget.js',\n LIVE: 'https://cdn.core.vittasinternational.com/latest/widget.js',\n} as const;\n\n/**\n * Expected postMessage origin when the payment iframe sends results back.\n * Must match the origin of the page loaded in the iframe (CDN-hosted frame).\n */\nexport const WIDGET_ORIGIN: Readonly<Record<WidgetMode, string>> = {\n TEST: 'https://dev-cdn.core.vittasinternational.com',\n LIVE: 'https://cdn.core.vittasinternational.com',\n} as const satisfies Readonly<Record<WidgetMode, string>>;\n","import { CDN_SCRIPT_URLS } from '../constants.js';\nimport type { WidgetMode } from '../types.js';\n\nconst pending = new Map<string, Promise<void>>();\n\n/**\n * Dynamically loads the Vittas CDN widget.js script once per mode.\n * Subsequent calls for the same mode return the cached promise.\n */\nexport function loadWidgetScript(mode: WidgetMode): Promise<void> {\n const src = CDN_SCRIPT_URLS[mode];\n\n // Already injected into the document\n if (document.querySelector(`script[src=\"${src}\"]`)) {\n return Promise.resolve();\n }\n\n // In-flight load — reuse the same promise\n const existing = pending.get(src);\n if (existing !== undefined) return existing;\n\n const promise = new Promise<void>((resolve, reject) => {\n const script = document.createElement('script');\n script.src = src;\n script.async = true;\n script.onload = () => {\n pending.delete(src);\n resolve();\n };\n script.onerror = () => {\n pending.delete(src);\n script.remove();\n reject(new Error(`Failed to load Vittas widget script: ${src}`));\n };\n document.head.appendChild(script);\n });\n\n pending.set(src, promise);\n return promise;\n}\n","import { useCallback, useRef, useState } from 'react';\nimport type { UseVittasPaymentOptions, UseVittasPaymentReturn } from '../types.js';\nimport { loadWidgetScript } from '../utils/loadWidgetScript.js';\n\nexport function useVittasPayment(options: UseVittasPaymentOptions): UseVittasPaymentReturn {\n const [isOpen, setIsOpen] = useState(false);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const open = useCallback(() => {\n const config = optionsRef.current;\n\n setIsOpen(true);\n\n loadWidgetScript(config.mode)\n .then(() => {\n if (typeof window.VittasPay?.init !== 'function') {\n throw new Error('window.VittasPay.init is not available after script load.');\n }\n\n window.VittasPay.init({\n clientSecret: config.clientSecret,\n onSuccess: (reference) => {\n setIsOpen(false);\n void config.onSuccess?.(reference);\n },\n onError: (err) => {\n setIsOpen(false);\n void config.onError?.(err);\n },\n onCancel: () => {\n setIsOpen(false);\n config.onCancel?.();\n },\n });\n })\n .catch((err: unknown) => {\n setIsOpen(false);\n void config.onError?.({\n message: err instanceof Error ? err.message : 'Failed to load payment widget.',\n status: 'failed',\n });\n });\n }, []);\n\n return { open, isOpen };\n}\n","import type { PaymentWidgetProps } from '../types.js';\nimport { useVittasPayment } from '../hooks/useVittasPayment.js';\n\nexport function PaymentWidget({\n mode,\n clientSecret,\n onSuccess,\n onError,\n onCancel,\n children,\n className,\n style,\n disabled = false,\n}: PaymentWidgetProps) {\n const { open } = useVittasPayment({\n mode,\n clientSecret,\n ...(onSuccess !== undefined && { onSuccess }),\n ...(onError !== undefined && { onError }),\n ...(onCancel !== undefined && { onCancel }),\n });\n\n if (children) {\n return (\n <span\n onClick={disabled ? undefined : open}\n style={{ display: 'contents', cursor: disabled ? 'not-allowed' : 'pointer', ...style }}\n className={className}\n >\n {children}\n </span>\n );\n }\n\n return (\n <button\n type=\"button\"\n onClick={open}\n disabled={disabled}\n className={className}\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '8px',\n padding: '12px 24px',\n background: disabled ? '#94a3b8' : '#1a56db',\n color: '#ffffff',\n fontSize: '15px',\n fontWeight: 600,\n lineHeight: 1,\n border: 'none',\n borderRadius: '8px',\n cursor: disabled ? 'not-allowed' : 'pointer',\n userSelect: 'none',\n transition: 'background 150ms ease',\n ...style,\n }}\n >\n Pay Now\n </button>\n );\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@vittascore/payment-widget-react",
3
+ "version": "0.1.0",
4
+ "description": "Official Vittas payment widget for React",
5
+ "keywords": [
6
+ "vittas",
7
+ "payment",
8
+ "widget",
9
+ "react",
10
+ "typescript"
11
+ ],
12
+ "homepage": "https://github.com/Vittas-Inc/vittas-core-payment-widget-react#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/Vittas-Inc/vittas-core-payment-widget-react/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/Vittas-Inc/vittas-core-payment-widget-react.git"
19
+ },
20
+ "license": "MIT",
21
+ "type": "module",
22
+ "main": "./dist/index.cjs",
23
+ "module": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "import": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ },
31
+ "require": {
32
+ "types": "./dist/index.d.cts",
33
+ "default": "./dist/index.cjs"
34
+ }
35
+ }
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "CHANGELOG.md"
40
+ ],
41
+ "scripts": {
42
+ "build": "tsup",
43
+ "dev": "tsup --watch",
44
+ "type-check": "tsc --noEmit",
45
+ "lint": "eslint src",
46
+ "format": "prettier --write src",
47
+ "prepublishOnly": "npm run type-check && npm run build"
48
+ },
49
+ "peerDependencies": {
50
+ "react": ">=17.0.0",
51
+ "react-dom": ">=17.0.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/react": "^18.3.23",
55
+ "@types/react-dom": "^18.3.7",
56
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
57
+ "@typescript-eslint/parser": "^8.0.0",
58
+ "eslint": "^9.0.0",
59
+ "prettier": "^3.4.2",
60
+ "react": "^18.3.1",
61
+ "react-dom": "^18.3.1",
62
+ "tsup": "^8.5.0",
63
+ "typescript": "^5.8.3"
64
+ },
65
+ "publishConfig": {
66
+ "access": "public",
67
+ "registry": "https://registry.npmjs.org/"
68
+ }
69
+ }