@blocklet/payment-react-headless 1.29.8 → 1.29.9
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/es/checkout/hooks/useCustomerForm.js +2 -0
- package/es/checkout/hooks/useSubmit.js +23 -0
- package/lib/checkout/hooks/useCustomerForm.js +6 -2
- package/lib/checkout/hooks/useSubmit.js +26 -0
- package/package.json +3 -3
- package/src/checkout/hooks/useCustomerForm.ts +7 -2
- package/src/checkout/hooks/useSubmit.ts +42 -0
|
@@ -49,6 +49,7 @@ export function useCustomerForm(sessionData, currencyId, methodId) {
|
|
|
49
49
|
}));
|
|
50
50
|
}
|
|
51
51
|
} catch {
|
|
52
|
+
setPrefetched(true);
|
|
52
53
|
}
|
|
53
54
|
};
|
|
54
55
|
fetchCustomer();
|
|
@@ -105,6 +106,7 @@ export function useCustomerForm(sessionData, currencyId, methodId) {
|
|
|
105
106
|
}));
|
|
106
107
|
}
|
|
107
108
|
} catch {
|
|
109
|
+
setPrefetched(true);
|
|
108
110
|
}
|
|
109
111
|
});
|
|
110
112
|
return {
|
|
@@ -105,6 +105,29 @@ export function useSubmit(sessionData, sessionId, currencyId, isStripe, isCredit
|
|
|
105
105
|
setContext({ type: "error", message: getErrorMessage(err) || "Payment verification failed" });
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (completedDetectedRef.current || !sessionId) return;
|
|
110
|
+
if (status !== "idle" && status !== "waiting_stripe") return;
|
|
111
|
+
let redirectStatus = null;
|
|
112
|
+
let hasPaymentIntent = false;
|
|
113
|
+
try {
|
|
114
|
+
const params = new URLSearchParams(window.location.search);
|
|
115
|
+
redirectStatus = params.get("redirect_status");
|
|
116
|
+
hasPaymentIntent = !!params.get("payment_intent");
|
|
117
|
+
} catch {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (!redirectStatus && !hasPaymentIntent) return;
|
|
121
|
+
completedDetectedRef.current = true;
|
|
122
|
+
if (redirectStatus === "failed" || redirectStatus === "canceled") {
|
|
123
|
+
setStatus("failed");
|
|
124
|
+
setContext({ type: "error", message: "Payment was not completed. Please try again." });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
setStatus("submitting");
|
|
128
|
+
setContext(null);
|
|
129
|
+
handleCompletion();
|
|
130
|
+
}, [sessionId]);
|
|
108
131
|
useEffect(() => {
|
|
109
132
|
if (status !== "completed" || !sessionId) return void 0;
|
|
110
133
|
const hasVendors = session?.line_items?.some(
|
|
@@ -62,7 +62,9 @@ function useCustomerForm(sessionData, currencyId, methodId) {
|
|
|
62
62
|
}
|
|
63
63
|
}));
|
|
64
64
|
}
|
|
65
|
-
} catch {
|
|
65
|
+
} catch {
|
|
66
|
+
setPrefetched(true);
|
|
67
|
+
}
|
|
66
68
|
};
|
|
67
69
|
fetchCustomer();
|
|
68
70
|
}, [session?.id]);
|
|
@@ -124,7 +126,9 @@ function useCustomerForm(sessionData, currencyId, methodId) {
|
|
|
124
126
|
}
|
|
125
127
|
}));
|
|
126
128
|
}
|
|
127
|
-
} catch {
|
|
129
|
+
} catch {
|
|
130
|
+
setPrefetched(true);
|
|
131
|
+
}
|
|
128
132
|
});
|
|
129
133
|
return {
|
|
130
134
|
fields,
|
|
@@ -104,6 +104,32 @@ function useSubmit(sessionData, sessionId, currencyId, isStripe, isCredit, isDon
|
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
});
|
|
107
|
+
(0, _react.useEffect)(() => {
|
|
108
|
+
if (completedDetectedRef.current || !sessionId) return;
|
|
109
|
+
if (status !== "idle" && status !== "waiting_stripe") return;
|
|
110
|
+
let redirectStatus = null;
|
|
111
|
+
let hasPaymentIntent = false;
|
|
112
|
+
try {
|
|
113
|
+
const params = new URLSearchParams(window.location.search);
|
|
114
|
+
redirectStatus = params.get("redirect_status");
|
|
115
|
+
hasPaymentIntent = !!params.get("payment_intent");
|
|
116
|
+
} catch {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!redirectStatus && !hasPaymentIntent) return;
|
|
120
|
+
completedDetectedRef.current = true;
|
|
121
|
+
if (redirectStatus === "failed" || redirectStatus === "canceled") {
|
|
122
|
+
setStatus("failed");
|
|
123
|
+
setContext({
|
|
124
|
+
type: "error",
|
|
125
|
+
message: "Payment was not completed. Please try again."
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
setStatus("submitting");
|
|
130
|
+
setContext(null);
|
|
131
|
+
handleCompletion();
|
|
132
|
+
}, [sessionId]);
|
|
107
133
|
(0, _react.useEffect)(() => {
|
|
108
134
|
if (status !== "completed" || !sessionId) return void 0;
|
|
109
135
|
const hasVendors = session?.line_items?.some(item => !!item?.price?.product?.vendor_config?.length);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/payment-react-headless",
|
|
3
|
-
"version": "1.29.
|
|
3
|
+
"version": "1.29.9",
|
|
4
4
|
"description": "Headless React hooks for payment-kit checkout",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@arcblock/ws": "^1.30.24",
|
|
37
37
|
"@blocklet/js-sdk": "workspace:*",
|
|
38
|
-
"@blocklet/payment-types": "1.29.
|
|
38
|
+
"@blocklet/payment-types": "1.29.9",
|
|
39
39
|
"@ocap/util": "^1.30.24",
|
|
40
40
|
"ahooks": "^3.8.5",
|
|
41
41
|
"google-libphonenumber": "^3.2.42",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"publishConfig": {
|
|
61
61
|
"access": "public"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "249bd9bf3c6cc54f77c260edb4b4d3c0be60f126"
|
|
64
64
|
}
|
|
@@ -82,7 +82,11 @@ export function useCustomerForm(
|
|
|
82
82
|
}));
|
|
83
83
|
}
|
|
84
84
|
} catch {
|
|
85
|
-
//
|
|
85
|
+
// /customers/me is best-effort. Still mark prefetch as attempted so the
|
|
86
|
+
// customer-info card renders an editable form (letting the user fill the
|
|
87
|
+
// required name/email manually) instead of staying hidden forever — e.g.
|
|
88
|
+
// when the endpoint 403s under embedded (no resolvable payment session).
|
|
89
|
+
setPrefetched(true);
|
|
86
90
|
}
|
|
87
91
|
};
|
|
88
92
|
|
|
@@ -149,7 +153,8 @@ export function useCustomerForm(
|
|
|
149
153
|
}));
|
|
150
154
|
}
|
|
151
155
|
} catch {
|
|
152
|
-
//
|
|
156
|
+
// Best-effort (see prefetch above) — mark attempted so the form still renders.
|
|
157
|
+
setPrefetched(true);
|
|
153
158
|
}
|
|
154
159
|
});
|
|
155
160
|
|
|
@@ -197,6 +197,48 @@ export function useSubmit(
|
|
|
197
197
|
}
|
|
198
198
|
});
|
|
199
199
|
|
|
200
|
+
// Stripe redirect fallback: when confirmPayment redirects back to return_url
|
|
201
|
+
// (3DS or redirect-based methods), the page fully reloads — onConfirm →
|
|
202
|
+
// stripeConfirm never runs, and the completion event only arrives via the
|
|
203
|
+
// WebSocket relay, which is unreachable on embedded/no-relay runtimes. Stripe
|
|
204
|
+
// appends redirect_status / payment_intent to the return URL; detect it and
|
|
205
|
+
// start polling until the backend reconciles, instead of getting stuck on
|
|
206
|
+
// loading until the user manually refreshes.
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (completedDetectedRef.current || !sessionId) return;
|
|
209
|
+
if (status !== 'idle' && status !== 'waiting_stripe') return;
|
|
210
|
+
let redirectStatus: string | null = null;
|
|
211
|
+
let hasPaymentIntent = false;
|
|
212
|
+
try {
|
|
213
|
+
const params = new URLSearchParams(window.location.search);
|
|
214
|
+
redirectStatus = params.get('redirect_status');
|
|
215
|
+
hasPaymentIntent = !!params.get('payment_intent');
|
|
216
|
+
} catch {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Not a Stripe redirect return — nothing to reconcile.
|
|
220
|
+
if (!redirectStatus && !hasPaymentIntent) return;
|
|
221
|
+
|
|
222
|
+
completedDetectedRef.current = true;
|
|
223
|
+
|
|
224
|
+
// A failed/canceled redirect ALSO carries `payment_intent`, so the bare presence
|
|
225
|
+
// of that param is not a success signal. Surface the failure immediately instead
|
|
226
|
+
// of polling a session that will never reach paid until the 5-minute timeout.
|
|
227
|
+
if (redirectStatus === 'failed' || redirectStatus === 'canceled') {
|
|
228
|
+
setStatus('failed');
|
|
229
|
+
setContext({ type: 'error', message: 'Payment was not completed. Please try again.' });
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// succeeded / pending / (param present without an explicit status) → the backend
|
|
234
|
+
// is the source of truth; poll until it reconciles. waitForCheckoutComplete also
|
|
235
|
+
// early-throws on a surfaced last_payment_error, so a hard decline still ends fast.
|
|
236
|
+
setStatus('submitting');
|
|
237
|
+
setContext(null);
|
|
238
|
+
handleCompletion();
|
|
239
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
240
|
+
}, [sessionId]);
|
|
241
|
+
|
|
200
242
|
// Vendor order polling after completion
|
|
201
243
|
useEffect(() => {
|
|
202
244
|
if (status !== 'completed' || !sessionId) return undefined;
|