@xpayeg/react 1.0.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/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +824 -0
- package/dist/index.cjs +576 -0
- package/dist/index.d.cts +239 -0
- package/dist/index.d.mts +239 -0
- package/dist/index.mjs +568 -0
- package/package.json +76 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let react = require("react");
|
|
3
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
4
|
+
//#region src/context.tsx
|
|
5
|
+
const XPayContext = (0, react.createContext)(null);
|
|
6
|
+
const ElementsContext = (0, react.createContext)(null);
|
|
7
|
+
/** Build a minimal XPayError for SDK-level failures (elements not initialized, etc.) */
|
|
8
|
+
function notInitializedError() {
|
|
9
|
+
return {
|
|
10
|
+
type: "api_error",
|
|
11
|
+
code: null,
|
|
12
|
+
message: "Elements not initialized",
|
|
13
|
+
param: null,
|
|
14
|
+
docUrl: null,
|
|
15
|
+
declineCode: null,
|
|
16
|
+
adviceCode: null,
|
|
17
|
+
chargeId: null,
|
|
18
|
+
paymentMethodId: null,
|
|
19
|
+
paymentMethodType: null,
|
|
20
|
+
paymentMethod: null
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const CheckoutContext = (0, react.createContext)(null);
|
|
24
|
+
/**
|
|
25
|
+
* Provides XPay context to all child components.
|
|
26
|
+
*
|
|
27
|
+
* Accepts either a resolved XPay instance or a Promise (from `loadXPay()`).
|
|
28
|
+
* When `options` (with `clientSecret`) is provided, creates an Elements instance
|
|
29
|
+
* and fetches session data. Use `useCheckout()` in child components to access
|
|
30
|
+
* session data and action methods.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* import { loadXPay } from "@xpayeg/sdk";
|
|
35
|
+
* import { XPayProvider, useCheckout, PaymentElement } from "@xpayeg/react";
|
|
36
|
+
*
|
|
37
|
+
* const xpayPromise = loadXPay("pk_test_xxx");
|
|
38
|
+
*
|
|
39
|
+
* function App() {
|
|
40
|
+
* return (
|
|
41
|
+
* <XPayProvider xpay={xpayPromise} options={{ clientSecret }}>
|
|
42
|
+
* <CheckoutForm />
|
|
43
|
+
* </XPayProvider>
|
|
44
|
+
* );
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
function XPayProvider({ xpay: xpayProp, options, children }) {
|
|
49
|
+
const [resolvedXPay, setResolvedXPay] = (0, react.useState)(xpayProp && !(xpayProp instanceof Promise) ? xpayProp : null);
|
|
50
|
+
const [xpayError, setXpayError] = (0, react.useState)(null);
|
|
51
|
+
(0, react.useEffect)(() => {
|
|
52
|
+
if (!xpayProp) return;
|
|
53
|
+
if (!(xpayProp instanceof Promise)) {
|
|
54
|
+
setResolvedXPay(xpayProp);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let cancelled = false;
|
|
58
|
+
xpayProp.then((instance) => {
|
|
59
|
+
if (!cancelled) setResolvedXPay(instance);
|
|
60
|
+
}).catch((err) => {
|
|
61
|
+
if (!cancelled) setXpayError(err instanceof Error ? err.message : "Failed to load XPay");
|
|
62
|
+
});
|
|
63
|
+
return () => {
|
|
64
|
+
cancelled = true;
|
|
65
|
+
};
|
|
66
|
+
}, [xpayProp]);
|
|
67
|
+
const elements = (0, react.useMemo)(() => {
|
|
68
|
+
if (!resolvedXPay || !options?.clientSecret) return null;
|
|
69
|
+
return resolvedXPay.elements(options);
|
|
70
|
+
}, [resolvedXPay, options?.clientSecret]);
|
|
71
|
+
const [session, setSession] = (0, react.useState)(null);
|
|
72
|
+
const [sessionError, setSessionError] = (0, react.useState)(null);
|
|
73
|
+
(0, react.useEffect)(() => {
|
|
74
|
+
if (!elements) return;
|
|
75
|
+
const onReady = (data) => {
|
|
76
|
+
setSession(data.session);
|
|
77
|
+
};
|
|
78
|
+
const onChange = (data) => {
|
|
79
|
+
const newSession = data;
|
|
80
|
+
if (newSession) setSession(newSession);
|
|
81
|
+
};
|
|
82
|
+
const onError = (data) => {
|
|
83
|
+
const d = data;
|
|
84
|
+
setSessionError(d.message || d.error || "Failed to load session");
|
|
85
|
+
};
|
|
86
|
+
elements.on("ready", onReady);
|
|
87
|
+
elements.on("change", onChange);
|
|
88
|
+
elements.on("loaderror", onError);
|
|
89
|
+
}, [elements]);
|
|
90
|
+
const checkoutState = (0, react.useMemo)(() => {
|
|
91
|
+
const errorMsg = xpayError || sessionError;
|
|
92
|
+
if (errorMsg) return {
|
|
93
|
+
type: "error",
|
|
94
|
+
error: { message: errorMsg }
|
|
95
|
+
};
|
|
96
|
+
if (!resolvedXPay || !elements || !session) return { type: "loading" };
|
|
97
|
+
return {
|
|
98
|
+
type: "success",
|
|
99
|
+
session,
|
|
100
|
+
elements,
|
|
101
|
+
xpay: resolvedXPay
|
|
102
|
+
};
|
|
103
|
+
}, [
|
|
104
|
+
resolvedXPay,
|
|
105
|
+
elements,
|
|
106
|
+
session,
|
|
107
|
+
xpayError,
|
|
108
|
+
sessionError
|
|
109
|
+
]);
|
|
110
|
+
const xpayValue = (0, react.useMemo)(() => resolvedXPay, [resolvedXPay]);
|
|
111
|
+
const elementsValue = (0, react.useMemo)(() => elements, [elements]);
|
|
112
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(XPayContext.Provider, {
|
|
113
|
+
value: xpayValue,
|
|
114
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ElementsContext.Provider, {
|
|
115
|
+
value: elementsValue,
|
|
116
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckoutContext.Provider, {
|
|
117
|
+
value: checkoutState,
|
|
118
|
+
children
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Access the checkout state — a tagged union of `loading`, `error`, or `success`.
|
|
125
|
+
*
|
|
126
|
+
* On success, returns a `checkout` object (type `Checkout`) that merges
|
|
127
|
+
* session data with action methods (confirm, promo codes, quantities, etc.).
|
|
128
|
+
*
|
|
129
|
+
* @returns `UseCheckoutResult` — narrow the type by checking `result.type`
|
|
130
|
+
* @throws Error if used outside of `<XPayProvider>`
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```tsx
|
|
134
|
+
* function CheckoutForm() {
|
|
135
|
+
* const state = useCheckout();
|
|
136
|
+
*
|
|
137
|
+
* if (state.type === "loading") return <Skeleton />;
|
|
138
|
+
* if (state.type === "error") return <p>{state.error.message}</p>;
|
|
139
|
+
*
|
|
140
|
+
* const { checkout } = state;
|
|
141
|
+
* return <button onClick={() => checkout.confirm()}>
|
|
142
|
+
* Pay {checkout.currency} {checkout.amountTotal}
|
|
143
|
+
* </button>;
|
|
144
|
+
* }
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
function useCheckout() {
|
|
148
|
+
const ctx = (0, react.useContext)(CheckoutContext);
|
|
149
|
+
if (!ctx) throw new Error("Could not find XPay context. Wrap the part of your app that calls useCheckout() in an <XPayProvider> provider.");
|
|
150
|
+
const { confirmFn, applyPromoFn, removePromoFn, updateLineItemQtyFn, submitFn, fetchUpdatesFn, changeAppearanceFn, onChangeFn, getElementsFn } = useCheckoutActions(ctx);
|
|
151
|
+
return (0, react.useMemo)(() => {
|
|
152
|
+
if (ctx.type === "loading") return { type: "loading" };
|
|
153
|
+
if (ctx.type === "error") return {
|
|
154
|
+
type: "error",
|
|
155
|
+
error: ctx.error
|
|
156
|
+
};
|
|
157
|
+
return {
|
|
158
|
+
type: "success",
|
|
159
|
+
checkout: {
|
|
160
|
+
...ctx.session,
|
|
161
|
+
confirm: confirmFn,
|
|
162
|
+
applyPromotionCode: applyPromoFn,
|
|
163
|
+
removePromotionCode: removePromoFn,
|
|
164
|
+
updateLineItemQuantity: updateLineItemQtyFn,
|
|
165
|
+
submit: submitFn,
|
|
166
|
+
fetchUpdates: fetchUpdatesFn,
|
|
167
|
+
changeAppearance: changeAppearanceFn,
|
|
168
|
+
on: onChangeFn,
|
|
169
|
+
getElements: getElementsFn
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}, [
|
|
173
|
+
ctx,
|
|
174
|
+
confirmFn,
|
|
175
|
+
applyPromoFn,
|
|
176
|
+
removePromoFn,
|
|
177
|
+
updateLineItemQtyFn,
|
|
178
|
+
submitFn,
|
|
179
|
+
fetchUpdatesFn,
|
|
180
|
+
changeAppearanceFn,
|
|
181
|
+
onChangeFn,
|
|
182
|
+
getElementsFn
|
|
183
|
+
]);
|
|
184
|
+
}
|
|
185
|
+
/** Build stable action callbacks from internal state */
|
|
186
|
+
function useCheckoutActions(ctx) {
|
|
187
|
+
const elements = ctx.type === "success" ? ctx.elements : null;
|
|
188
|
+
const xpay = ctx.type === "success" ? ctx.xpay : null;
|
|
189
|
+
return {
|
|
190
|
+
confirmFn: (0, react.useCallback)(async (opts) => {
|
|
191
|
+
if (!xpay || !elements) return {
|
|
192
|
+
type: "error",
|
|
193
|
+
error: {
|
|
194
|
+
type: "invalid_request_error",
|
|
195
|
+
code: null,
|
|
196
|
+
declineCode: null,
|
|
197
|
+
adviceCode: null,
|
|
198
|
+
message: "XPay not initialized",
|
|
199
|
+
docUrl: null,
|
|
200
|
+
chargeId: null,
|
|
201
|
+
paymentMethodId: null,
|
|
202
|
+
paymentMethodType: null,
|
|
203
|
+
paymentMethod: null
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
return xpay.confirmPayment({
|
|
207
|
+
...opts,
|
|
208
|
+
elements
|
|
209
|
+
});
|
|
210
|
+
}, [xpay, elements]),
|
|
211
|
+
applyPromoFn: (0, react.useCallback)(async (code) => {
|
|
212
|
+
if (!elements) return {
|
|
213
|
+
type: "error",
|
|
214
|
+
error: notInitializedError()
|
|
215
|
+
};
|
|
216
|
+
return elements.applyPromotionCode(code);
|
|
217
|
+
}, [elements]),
|
|
218
|
+
removePromoFn: (0, react.useCallback)(async () => {
|
|
219
|
+
if (!elements) return {
|
|
220
|
+
type: "error",
|
|
221
|
+
error: notInitializedError()
|
|
222
|
+
};
|
|
223
|
+
return elements.removePromotionCode();
|
|
224
|
+
}, [elements]),
|
|
225
|
+
updateLineItemQtyFn: (0, react.useCallback)(async (args) => {
|
|
226
|
+
if (!elements) return {
|
|
227
|
+
type: "error",
|
|
228
|
+
error: notInitializedError()
|
|
229
|
+
};
|
|
230
|
+
return elements.updateLineItemQuantity(args);
|
|
231
|
+
}, [elements]),
|
|
232
|
+
submitFn: (0, react.useCallback)(async () => {
|
|
233
|
+
if (!elements) return { error: notInitializedError() };
|
|
234
|
+
return elements.submit();
|
|
235
|
+
}, [elements]),
|
|
236
|
+
fetchUpdatesFn: (0, react.useCallback)(async () => {
|
|
237
|
+
if (!elements) return {
|
|
238
|
+
type: "error",
|
|
239
|
+
error: notInitializedError()
|
|
240
|
+
};
|
|
241
|
+
return elements.fetchUpdates();
|
|
242
|
+
}, [elements]),
|
|
243
|
+
changeAppearanceFn: (0, react.useCallback)((appearance) => {
|
|
244
|
+
elements?.changeAppearance(appearance);
|
|
245
|
+
}, [elements]),
|
|
246
|
+
onChangeFn: (0, react.useCallback)(((event, handler) => {
|
|
247
|
+
elements?.on(event, handler);
|
|
248
|
+
}), [elements]),
|
|
249
|
+
getElementsFn: (0, react.useCallback)(() => {
|
|
250
|
+
if (!elements) throw new Error("Elements not initialized");
|
|
251
|
+
return elements;
|
|
252
|
+
}, [elements])
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Access the raw XPay SDK instance. Returns `null` while the SDK is loading.
|
|
257
|
+
*
|
|
258
|
+
* Most merchants should use `useCheckout()` instead. Use `useXPay()` only when
|
|
259
|
+
* you need direct access to `xpay.confirmPayment()` or `xpay.checkout()`.
|
|
260
|
+
*/
|
|
261
|
+
function useXPay() {
|
|
262
|
+
return (0, react.useContext)(XPayContext);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Access the Elements instance. Returns `null` while the session is loading.
|
|
266
|
+
*
|
|
267
|
+
* Use this for direct element management. In most cases, use `useCheckout()` instead
|
|
268
|
+
* which provides both session data and action methods.
|
|
269
|
+
*/
|
|
270
|
+
function useElements() {
|
|
271
|
+
return (0, react.useContext)(ElementsContext);
|
|
272
|
+
}
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/hooks.ts
|
|
275
|
+
/**
|
|
276
|
+
* Convenience hook for payment confirmation.
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```tsx
|
|
280
|
+
* const { confirmPayment, isConfirming } = useConfirmPayment();
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
function useConfirmPayment() {
|
|
284
|
+
const result = useCheckout();
|
|
285
|
+
if (result.type !== "success") return {
|
|
286
|
+
confirmPayment: async () => ({
|
|
287
|
+
type: "error",
|
|
288
|
+
error: {
|
|
289
|
+
message: "Checkout not ready",
|
|
290
|
+
code: null
|
|
291
|
+
}
|
|
292
|
+
}),
|
|
293
|
+
isConfirming: false
|
|
294
|
+
};
|
|
295
|
+
return {
|
|
296
|
+
confirmPayment: result.checkout.confirm,
|
|
297
|
+
isConfirming: false
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/utils/useAttachEvent.ts
|
|
302
|
+
/**
|
|
303
|
+
* Attach an event listener to an element without causing listener churn on re-renders.
|
|
304
|
+
*
|
|
305
|
+
* Stores the callback in a ref so the actual listener remains stable.
|
|
306
|
+
* Inspired by Stripe's useAttachEvent pattern.
|
|
307
|
+
*/
|
|
308
|
+
function useAttachEvent(element, event, cb) {
|
|
309
|
+
const cbDefined = !!cb;
|
|
310
|
+
const cbRef = (0, react.useRef)(cb);
|
|
311
|
+
(0, react.useEffect)(() => {
|
|
312
|
+
cbRef.current = cb;
|
|
313
|
+
}, [cb]);
|
|
314
|
+
(0, react.useEffect)(() => {
|
|
315
|
+
if (!cbDefined || !element) return;
|
|
316
|
+
const decoratedCb = (...args) => {
|
|
317
|
+
if (cbRef.current) cbRef.current(...args);
|
|
318
|
+
};
|
|
319
|
+
element.on(event, decoratedCb);
|
|
320
|
+
return () => {
|
|
321
|
+
element.off(event, decoratedCb);
|
|
322
|
+
};
|
|
323
|
+
}, [
|
|
324
|
+
cbDefined,
|
|
325
|
+
event,
|
|
326
|
+
element
|
|
327
|
+
]);
|
|
328
|
+
}
|
|
329
|
+
//#endregion
|
|
330
|
+
//#region src/utils/usePrevious.ts
|
|
331
|
+
/**
|
|
332
|
+
* Returns the previous value of a variable.
|
|
333
|
+
* Useful for detecting prop changes between renders.
|
|
334
|
+
*/
|
|
335
|
+
function usePrevious(value) {
|
|
336
|
+
const ref = (0, react.useRef)(void 0);
|
|
337
|
+
(0, react.useEffect)(() => {
|
|
338
|
+
ref.current = value;
|
|
339
|
+
}, [value]);
|
|
340
|
+
return ref.current;
|
|
341
|
+
}
|
|
342
|
+
//#endregion
|
|
343
|
+
//#region src/utils/extractAllowedOptionsUpdates.ts
|
|
344
|
+
/**
|
|
345
|
+
* Extract only the changed, mutable options from a new options object.
|
|
346
|
+
*
|
|
347
|
+
* Returns null if nothing changed. Warns if immutable keys are modified.
|
|
348
|
+
* Inspired by Stripe's extractAllowedOptionsUpdates pattern.
|
|
349
|
+
*/
|
|
350
|
+
function extractAllowedOptionsUpdates(options, prevOptions, immutableKeys) {
|
|
351
|
+
if (!options) return null;
|
|
352
|
+
let updates = null;
|
|
353
|
+
for (const key of Object.keys(options)) {
|
|
354
|
+
const isUpdated = !prevOptions || !isEqual(options[key], prevOptions[key]);
|
|
355
|
+
if (immutableKeys.includes(key)) {
|
|
356
|
+
if (isUpdated && prevOptions) console.warn(`Unsupported prop change: options.${key} is not a mutable property.`);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (!isUpdated) continue;
|
|
360
|
+
updates = {
|
|
361
|
+
...updates || {},
|
|
362
|
+
[key]: options[key]
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return updates;
|
|
366
|
+
}
|
|
367
|
+
function isEqual(a, b) {
|
|
368
|
+
if (a === b) return true;
|
|
369
|
+
if (a == null || b == null) return false;
|
|
370
|
+
if (typeof a !== typeof b) return false;
|
|
371
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
372
|
+
if (a.length !== b.length) return false;
|
|
373
|
+
return a.every((v, i) => isEqual(v, b[i]));
|
|
374
|
+
}
|
|
375
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
376
|
+
const aObj = a;
|
|
377
|
+
const bObj = b;
|
|
378
|
+
const aKeys = Object.keys(aObj);
|
|
379
|
+
const bKeys = Object.keys(bObj);
|
|
380
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
381
|
+
return aKeys.every((key) => isEqual(aObj[key], bObj[key]));
|
|
382
|
+
}
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region src/utils/isServer.ts
|
|
387
|
+
/** True when running in a server environment (SSR/RSC) */
|
|
388
|
+
const isServer = typeof window === "undefined";
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/PaymentElement.tsx
|
|
391
|
+
const IMMUTABLE_OPTS$1 = [];
|
|
392
|
+
const PaymentElementClient = ({ options, onReady, onChange, onLoaderStart, onLoadError, className, id }) => {
|
|
393
|
+
const elements = useElements();
|
|
394
|
+
const containerRef = (0, react.useRef)(null);
|
|
395
|
+
const elementRef = (0, react.useRef)(null);
|
|
396
|
+
const [element, setElement] = (0, react.useState)(null);
|
|
397
|
+
const prevOptions = usePrevious(options);
|
|
398
|
+
(0, react.useLayoutEffect)(() => {
|
|
399
|
+
if (elementRef.current !== null || !elements || !containerRef.current) return;
|
|
400
|
+
const el = elements.create("payment", options);
|
|
401
|
+
elementRef.current = el;
|
|
402
|
+
setElement(el);
|
|
403
|
+
el.mount(containerRef.current);
|
|
404
|
+
}, [elements]);
|
|
405
|
+
(0, react.useLayoutEffect)(() => {
|
|
406
|
+
return () => {
|
|
407
|
+
if (elementRef.current) {
|
|
408
|
+
try {
|
|
409
|
+
elementRef.current.destroy();
|
|
410
|
+
} catch {}
|
|
411
|
+
elementRef.current = null;
|
|
412
|
+
}
|
|
413
|
+
setElement(null);
|
|
414
|
+
};
|
|
415
|
+
}, []);
|
|
416
|
+
(0, react.useEffect)(() => {
|
|
417
|
+
if (!element || !options) return;
|
|
418
|
+
const updates = extractAllowedOptionsUpdates(options, prevOptions, IMMUTABLE_OPTS$1);
|
|
419
|
+
if (updates && "update" in element) element.update(updates);
|
|
420
|
+
}, [
|
|
421
|
+
options,
|
|
422
|
+
prevOptions,
|
|
423
|
+
element
|
|
424
|
+
]);
|
|
425
|
+
useAttachEvent(element, "ready", onReady);
|
|
426
|
+
useAttachEvent(element, "change", onChange);
|
|
427
|
+
useAttachEvent(element, "loaderstart", onLoaderStart);
|
|
428
|
+
useAttachEvent(element, "loaderror", onLoadError);
|
|
429
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
430
|
+
ref: containerRef,
|
|
431
|
+
className,
|
|
432
|
+
id
|
|
433
|
+
});
|
|
434
|
+
};
|
|
435
|
+
/** SSR placeholder — renders an empty div for hydration */
|
|
436
|
+
const PaymentElementServer = ({ className, id }) => {
|
|
437
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
438
|
+
className,
|
|
439
|
+
id
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
/**
|
|
443
|
+
* Renders the XPay Payment Element — a full payment method selector with card form.
|
|
444
|
+
*
|
|
445
|
+
* Must be used inside `<XPayProvider>` with an `options` prop containing `clientSecret`.
|
|
446
|
+
* Handles mount/unmount lifecycle, StrictMode, and SSR automatically.
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```tsx
|
|
450
|
+
* <XPayProvider xpay={xpayPromise} options={{ clientSecret }}>
|
|
451
|
+
* <PaymentElement
|
|
452
|
+
* onChange={(e) => setPaymentReady(e.complete)}
|
|
453
|
+
* onLoadError={(e) => console.error(e.message)}
|
|
454
|
+
* />
|
|
455
|
+
* </XPayProvider>
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
458
|
+
const PaymentElement = isServer ? PaymentElementServer : PaymentElementClient;
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region src/CardElement.tsx
|
|
461
|
+
const IMMUTABLE_OPTS = [];
|
|
462
|
+
const CardElementClient = ({ options, onReady, onChange, onLoaderStart, onLoadError, className, id }) => {
|
|
463
|
+
const elements = useElements();
|
|
464
|
+
const containerRef = (0, react.useRef)(null);
|
|
465
|
+
const elementRef = (0, react.useRef)(null);
|
|
466
|
+
const [element, setElement] = (0, react.useState)(null);
|
|
467
|
+
const prevOptions = usePrevious(options);
|
|
468
|
+
(0, react.useLayoutEffect)(() => {
|
|
469
|
+
if (elementRef.current !== null || !elements || !containerRef.current) return;
|
|
470
|
+
const el = elements.create("card", options);
|
|
471
|
+
elementRef.current = el;
|
|
472
|
+
setElement(el);
|
|
473
|
+
el.mount(containerRef.current);
|
|
474
|
+
}, [elements]);
|
|
475
|
+
(0, react.useLayoutEffect)(() => {
|
|
476
|
+
return () => {
|
|
477
|
+
if (elementRef.current) {
|
|
478
|
+
try {
|
|
479
|
+
elementRef.current.destroy();
|
|
480
|
+
} catch {}
|
|
481
|
+
elementRef.current = null;
|
|
482
|
+
}
|
|
483
|
+
setElement(null);
|
|
484
|
+
};
|
|
485
|
+
}, []);
|
|
486
|
+
(0, react.useEffect)(() => {
|
|
487
|
+
if (!element || !options) return;
|
|
488
|
+
const updates = extractAllowedOptionsUpdates(options, prevOptions, IMMUTABLE_OPTS);
|
|
489
|
+
if (updates && "update" in element) element.update(updates);
|
|
490
|
+
}, [
|
|
491
|
+
options,
|
|
492
|
+
prevOptions,
|
|
493
|
+
element
|
|
494
|
+
]);
|
|
495
|
+
useAttachEvent(element, "ready", onReady);
|
|
496
|
+
useAttachEvent(element, "change", onChange);
|
|
497
|
+
useAttachEvent(element, "loaderstart", onLoaderStart);
|
|
498
|
+
useAttachEvent(element, "loaderror", onLoadError);
|
|
499
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
500
|
+
ref: containerRef,
|
|
501
|
+
className,
|
|
502
|
+
id
|
|
503
|
+
});
|
|
504
|
+
};
|
|
505
|
+
const CardElementServer = ({ className, id }) => {
|
|
506
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
507
|
+
className,
|
|
508
|
+
id
|
|
509
|
+
});
|
|
510
|
+
};
|
|
511
|
+
/**
|
|
512
|
+
* Renders the XPay Card Element — a card-only form (number, expiry, CVV).
|
|
513
|
+
*
|
|
514
|
+
* Use this when you handle payment method selection yourself.
|
|
515
|
+
* Must be used inside `<XPayProvider>` with an `options` prop containing `clientSecret`.
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
* ```tsx
|
|
519
|
+
* <CardElement
|
|
520
|
+
* onChange={(e) => setCardReady(e.complete)}
|
|
521
|
+
* />
|
|
522
|
+
* ```
|
|
523
|
+
*/
|
|
524
|
+
const CardElement = isServer ? CardElementServer : CardElementClient;
|
|
525
|
+
//#endregion
|
|
526
|
+
//#region src/CheckoutButton.tsx
|
|
527
|
+
/**
|
|
528
|
+
* Button that opens the drop-in checkout modal on click.
|
|
529
|
+
*
|
|
530
|
+
* Must be inside XPayProvider.
|
|
531
|
+
*
|
|
532
|
+
* @example
|
|
533
|
+
* ```tsx
|
|
534
|
+
* <XPayProvider xpay={xpay}>
|
|
535
|
+
* <CheckoutButton
|
|
536
|
+
* clientSecret="cs_test_abc_secret_xyz"
|
|
537
|
+
* checkoutOptions={{
|
|
538
|
+
* onComplete: (result) => router.push('/success'),
|
|
539
|
+
* onClose: () => console.log('Closed'),
|
|
540
|
+
* }}
|
|
541
|
+
* >
|
|
542
|
+
* Pay Now
|
|
543
|
+
* </CheckoutButton>
|
|
544
|
+
* </XPayProvider>
|
|
545
|
+
* ```
|
|
546
|
+
*/
|
|
547
|
+
const CheckoutButton = ({ clientSecret, children = "Pay", checkoutOptions, className, disabled }) => {
|
|
548
|
+
const xpay = useXPay();
|
|
549
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
550
|
+
type: "button",
|
|
551
|
+
onClick: (0, react.useCallback)(() => {
|
|
552
|
+
if (!xpay) return;
|
|
553
|
+
xpay.checkout({
|
|
554
|
+
clientSecret,
|
|
555
|
+
mode: "modal",
|
|
556
|
+
...checkoutOptions
|
|
557
|
+
}).open();
|
|
558
|
+
}, [
|
|
559
|
+
xpay,
|
|
560
|
+
clientSecret,
|
|
561
|
+
checkoutOptions
|
|
562
|
+
]),
|
|
563
|
+
disabled: disabled || !xpay,
|
|
564
|
+
className,
|
|
565
|
+
children
|
|
566
|
+
});
|
|
567
|
+
};
|
|
568
|
+
//#endregion
|
|
569
|
+
exports.CardElement = CardElement;
|
|
570
|
+
exports.CheckoutButton = CheckoutButton;
|
|
571
|
+
exports.PaymentElement = PaymentElement;
|
|
572
|
+
exports.XPayProvider = XPayProvider;
|
|
573
|
+
exports.useCheckout = useCheckout;
|
|
574
|
+
exports.useConfirmPayment = useConfirmPayment;
|
|
575
|
+
exports.useElements = useElements;
|
|
576
|
+
exports.useXPay = useXPay;
|