@djangocfg/payments 2.1.547 → 2.1.549
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/payments",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.549",
|
|
4
4
|
"description": "Provider-agnostic React payments module (Stripe-first): checkout state machine, Payment Element wrapper, close-guard context, mock adapter for keyless development",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"payments",
|
|
@@ -54,15 +54,15 @@
|
|
|
54
54
|
"@stripe/stripe-js": "^9.9.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@djangocfg/ui-core": "^2.1.
|
|
57
|
+
"@djangocfg/ui-core": "^2.1.549",
|
|
58
58
|
"lucide-react": "^0.545.0",
|
|
59
59
|
"react": "^19.0.0",
|
|
60
60
|
"react-dom": "^19.0.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@djangocfg/eslint-config": "^2.1.
|
|
64
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
65
|
-
"@djangocfg/ui-core": "^2.1.
|
|
63
|
+
"@djangocfg/eslint-config": "^2.1.549",
|
|
64
|
+
"@djangocfg/typescript-config": "^2.1.549",
|
|
65
|
+
"@djangocfg/ui-core": "^2.1.549",
|
|
66
66
|
"@storybook/react-vite": "^10.5.0",
|
|
67
67
|
"@types/node": "^25.9.5",
|
|
68
68
|
"@types/react": "19.2.15",
|
|
@@ -100,17 +100,34 @@ export function CheckoutForm({
|
|
|
100
100
|
const isDone = status === 'succeeded';
|
|
101
101
|
const needsAction = status === 'requires_action';
|
|
102
102
|
|
|
103
|
+
// A ref, because the disabled button is not a lock. `start` leaves status at
|
|
104
|
+
// `requires_payment` before `confirm` sets `processing`, so between the two
|
|
105
|
+
// awaits `isBusy` is false and the button is live again — and a second Enter
|
|
106
|
+
// there would skip `start` (intent now set) and confirm the SAME intent
|
|
107
|
+
// twice. A ref flips synchronously, with no render in between.
|
|
108
|
+
const submittingRef = React.useRef(false);
|
|
109
|
+
|
|
103
110
|
const handlePay = useCallback(
|
|
104
111
|
async (e: React.FormEvent) => {
|
|
105
112
|
e.preventDefault();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
if (submittingRef.current) return;
|
|
114
|
+
submittingRef.current = true;
|
|
115
|
+
try {
|
|
116
|
+
// Self-driving mode only: lazily create the one-time intent on first
|
|
117
|
+
// submit. In shared mode the parent already created the intent/subscription
|
|
118
|
+
// (start / startSubscription) — creating another here would duplicate it,
|
|
119
|
+
// so we go straight to confirm.
|
|
120
|
+
if (!isShared && !intent) {
|
|
121
|
+
// `start` reports its own failure through status/error. Confirming
|
|
122
|
+
// anyway would replace that reason with a generic "not ready to
|
|
123
|
+
// confirm", hiding why the payment could not be created.
|
|
124
|
+
const created = await start({ amount, currency, reference: _reference });
|
|
125
|
+
if (!created) return;
|
|
126
|
+
}
|
|
127
|
+
await confirm(elementsContext, returnUrl);
|
|
128
|
+
} finally {
|
|
129
|
+
submittingRef.current = false;
|
|
112
130
|
}
|
|
113
|
-
await confirm(elementsContext, returnUrl);
|
|
114
131
|
},
|
|
115
132
|
[isShared, intent, start, amount, currency, _reference, confirm, elementsContext, returnUrl],
|
|
116
133
|
);
|
package/src/hooks/useCheckout.ts
CHANGED
|
@@ -25,7 +25,8 @@ export interface UseCheckoutResult {
|
|
|
25
25
|
/** Set when the provider needs a redirect / 3DS step. */
|
|
26
26
|
nextActionUrl: string | null;
|
|
27
27
|
/** Step 1 (one-time): create the PaymentIntent → client_secret ready. */
|
|
28
|
-
|
|
28
|
+
/** Resolves with the created intent, or undefined when creation failed. */
|
|
29
|
+
start: (input: CreatePaymentInput) => Promise<PaymentIntent | undefined>;
|
|
29
30
|
/**
|
|
30
31
|
* Step 1 (subscription, deferred flow): create an `incomplete` Stripe
|
|
31
32
|
* subscription on the backend → client_secret + confirmType ready. `confirm`
|
|
@@ -76,9 +77,13 @@ export function useCheckout(): UseCheckoutResult {
|
|
|
76
77
|
intentRef.current = created;
|
|
77
78
|
setIntent(created);
|
|
78
79
|
setStatus(created.status === 'idle' ? 'requires_payment' : created.status);
|
|
80
|
+
// Returned so a caller can tell success from failure without reading
|
|
81
|
+
// state that has not re-rendered yet.
|
|
82
|
+
return created;
|
|
79
83
|
} catch (e) {
|
|
80
84
|
setStatus('failed');
|
|
81
85
|
setError(e instanceof Error ? e.message : 'Failed to start payment.');
|
|
86
|
+
return undefined;
|
|
82
87
|
}
|
|
83
88
|
},
|
|
84
89
|
[adapter],
|