@akinon/next 2.0.59-beta.0 → 2.0.59
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 +2 -2
- package/data/client/api.ts +9 -0
- package/data/client/checkout.ts +24 -44
- package/package.json +2 -2
- package/redux/middlewares/pre-order/address.ts +9 -1
- package/redux/middlewares/pre-order/delivery-option.ts +7 -0
- package/redux/middlewares/pre-order/payment-option.ts +7 -0
- package/redux/middlewares/pre-order/shipping-option.ts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
-
## 2.0.59
|
|
3
|
+
## 2.0.59
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- 9a1a2df: ZERO-4804: Port the deterministic checkout fix to v2 by removing the shared checkout abort controller and the 250 ms delayed validation, and guarding pre-order auto-select middlewares with pre_order preconditions
|
|
8
8
|
|
|
9
9
|
## 2.0.58
|
|
10
10
|
|
package/data/client/api.ts
CHANGED
|
@@ -33,6 +33,15 @@ const customBaseQuery: BaseQueryFn<
|
|
|
33
33
|
).length > 1
|
|
34
34
|
) {
|
|
35
35
|
api.abort('Mutation already in progress.');
|
|
36
|
+
|
|
37
|
+
// Short-circuit explicitly instead of relying on the abort to settle
|
|
38
|
+
// before the request reaches the network (ZERO-4638).
|
|
39
|
+
return {
|
|
40
|
+
error: {
|
|
41
|
+
status: 'CUSTOM_ERROR',
|
|
42
|
+
error: 'Mutation already in progress.'
|
|
43
|
+
} as FetchBaseQueryError
|
|
44
|
+
};
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
const baseQuery = fetchBaseQuery({
|
package/data/client/checkout.ts
CHANGED
|
@@ -36,12 +36,6 @@ import {
|
|
|
36
36
|
import { devLogger } from '@akinon/next/hooks/use-logger-context';
|
|
37
37
|
import { LogLevel } from '@akinon/next/hooks/use-logger';
|
|
38
38
|
|
|
39
|
-
const getLatestState = async (getState: () => any): Promise<any> => {
|
|
40
|
-
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
41
|
-
|
|
42
|
-
return getState();
|
|
43
|
-
};
|
|
44
|
-
|
|
45
39
|
const recentLogMessages = new Map<string, number>();
|
|
46
40
|
|
|
47
41
|
const getStore = async (): Promise<AppStore> => {
|
|
@@ -59,6 +53,10 @@ interface CheckoutResponse {
|
|
|
59
53
|
redirect_url?: string;
|
|
60
54
|
}
|
|
61
55
|
|
|
56
|
+
// Log-only diagnostics: checkout mutations are never aborted based on these
|
|
57
|
+
// checks anymore. Invalid auto-dispatches are prevented at their source in
|
|
58
|
+
// redux/middlewares/pre-order, and Commerce stays the authority for manual
|
|
59
|
+
// dispatches (ZERO-4638).
|
|
62
60
|
const validateCheckoutState = (
|
|
63
61
|
state: any,
|
|
64
62
|
validations: Array<{
|
|
@@ -66,17 +64,10 @@ const validateCheckoutState = (
|
|
|
66
64
|
errorMessage: string;
|
|
67
65
|
severity?: LogLevel;
|
|
68
66
|
data?: any;
|
|
69
|
-
action?: () => void;
|
|
70
67
|
}>
|
|
71
68
|
) => {
|
|
72
69
|
validations.forEach(
|
|
73
|
-
({
|
|
74
|
-
condition,
|
|
75
|
-
errorMessage,
|
|
76
|
-
severity = 'error',
|
|
77
|
-
data: logData,
|
|
78
|
-
action
|
|
79
|
-
}) => {
|
|
70
|
+
({ condition, errorMessage, severity = 'error', data: logData }) => {
|
|
80
71
|
if (condition(state)) {
|
|
81
72
|
const now = Date.now();
|
|
82
73
|
const lastLogged = recentLogMessages.get(errorMessage) || 0;
|
|
@@ -90,15 +81,12 @@ const validateCheckoutState = (
|
|
|
90
81
|
errorMessage,
|
|
91
82
|
logData || state.checkout?.preOrder
|
|
92
83
|
);
|
|
93
|
-
action?.();
|
|
94
84
|
break;
|
|
95
85
|
case 'warn':
|
|
96
86
|
devLogger.warn(errorMessage, logData || state.checkout?.preOrder);
|
|
97
|
-
action?.();
|
|
98
87
|
break;
|
|
99
88
|
default:
|
|
100
89
|
devLogger.info(errorMessage, logData || state.checkout?.preOrder);
|
|
101
|
-
action?.();
|
|
102
90
|
}
|
|
103
91
|
}
|
|
104
92
|
}
|
|
@@ -254,6 +242,12 @@ const completeMasterpassPayment = async (
|
|
|
254
242
|
|
|
255
243
|
let checkoutAbortController = new AbortController();
|
|
256
244
|
|
|
245
|
+
/**
|
|
246
|
+
* @deprecated Checkout mutations no longer share an abort signal; nothing in
|
|
247
|
+
* the core aborts this controller anymore. Premature auto-dispatches are
|
|
248
|
+
* prevented in redux/middlewares/pre-order instead (ZERO-4638). Kept only so
|
|
249
|
+
* existing external imports keep compiling.
|
|
250
|
+
*/
|
|
257
251
|
export const getCheckoutAbortSignal = () => {
|
|
258
252
|
if (checkoutAbortController.signal.aborted) {
|
|
259
253
|
checkoutAbortController = new AbortController();
|
|
@@ -261,11 +255,6 @@ export const getCheckoutAbortSignal = () => {
|
|
|
261
255
|
return checkoutAbortController.signal;
|
|
262
256
|
};
|
|
263
257
|
|
|
264
|
-
const abortCheckout = () => {
|
|
265
|
-
checkoutAbortController.abort();
|
|
266
|
-
checkoutAbortController = new AbortController();
|
|
267
|
-
};
|
|
268
|
-
|
|
269
258
|
export const checkoutApi = api.injectEndpoints({
|
|
270
259
|
endpoints: (build) => ({
|
|
271
260
|
fetchCheckout: build.query<CheckoutResponse, void>({
|
|
@@ -452,13 +441,12 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
452
441
|
method: 'POST',
|
|
453
442
|
body: {
|
|
454
443
|
delivery_option: String(pk)
|
|
455
|
-
}
|
|
456
|
-
signal: getCheckoutAbortSignal()
|
|
444
|
+
}
|
|
457
445
|
}),
|
|
458
446
|
async onQueryStarted(arg, { dispatch, queryFulfilled, getState }) {
|
|
459
447
|
dispatch(setShippingStepBusy(true));
|
|
460
448
|
|
|
461
|
-
const state =
|
|
449
|
+
const state = getState();
|
|
462
450
|
|
|
463
451
|
validateCheckoutState(state, [
|
|
464
452
|
{
|
|
@@ -468,8 +456,7 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
468
456
|
return preOrder?.basket?.basketitem_set?.length === 0;
|
|
469
457
|
},
|
|
470
458
|
errorMessage:
|
|
471
|
-
'Your shopping basket is empty. Please add items to your basket before selecting a delivery option.'
|
|
472
|
-
action: () => abortCheckout()
|
|
459
|
+
'Your shopping basket is empty. Please add items to your basket before selecting a delivery option.'
|
|
473
460
|
}
|
|
474
461
|
]);
|
|
475
462
|
|
|
@@ -490,13 +477,12 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
490
477
|
body: {
|
|
491
478
|
shipping_address: String(shippingAddressPk),
|
|
492
479
|
billing_address: String(billingAddressPk)
|
|
493
|
-
}
|
|
494
|
-
signal: getCheckoutAbortSignal()
|
|
480
|
+
}
|
|
495
481
|
}),
|
|
496
482
|
async onQueryStarted(arg, { dispatch, queryFulfilled, getState }) {
|
|
497
483
|
dispatch(setShippingStepBusy(true));
|
|
498
484
|
|
|
499
|
-
const state =
|
|
485
|
+
const state = getState();
|
|
500
486
|
|
|
501
487
|
validateCheckoutState(state, [
|
|
502
488
|
{
|
|
@@ -509,8 +495,7 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
509
495
|
: false;
|
|
510
496
|
},
|
|
511
497
|
errorMessage:
|
|
512
|
-
'You need to select a delivery option before setting your addresses. Dispatch setAddresses action after delivery option selection.'
|
|
513
|
-
action: () => abortCheckout()
|
|
498
|
+
'You need to select a delivery option before setting your addresses. Dispatch setAddresses action after delivery option selection.'
|
|
514
499
|
}
|
|
515
500
|
]);
|
|
516
501
|
|
|
@@ -530,13 +515,12 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
530
515
|
method: 'POST',
|
|
531
516
|
body: {
|
|
532
517
|
shipping_option: String(pk)
|
|
533
|
-
}
|
|
534
|
-
signal: getCheckoutAbortSignal()
|
|
518
|
+
}
|
|
535
519
|
}),
|
|
536
520
|
async onQueryStarted(arg, { dispatch, queryFulfilled, getState }) {
|
|
537
521
|
dispatch(setShippingStepBusy(true));
|
|
538
522
|
|
|
539
|
-
const state =
|
|
523
|
+
const state = getState();
|
|
540
524
|
|
|
541
525
|
validateCheckoutState(state, [
|
|
542
526
|
{
|
|
@@ -546,8 +530,7 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
546
530
|
return !preOrder?.billing_address;
|
|
547
531
|
},
|
|
548
532
|
errorMessage:
|
|
549
|
-
'You need to provide a billing address before selecting a shipping option. Dispatch setShippingOption action after billing address selection.'
|
|
550
|
-
action: () => abortCheckout()
|
|
533
|
+
'You need to provide a billing address before selecting a shipping option. Dispatch setShippingOption action after billing address selection.'
|
|
551
534
|
},
|
|
552
535
|
{
|
|
553
536
|
condition: (state) => {
|
|
@@ -556,8 +539,7 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
556
539
|
return !preOrder?.shipping_address;
|
|
557
540
|
},
|
|
558
541
|
errorMessage:
|
|
559
|
-
'You need to provide a shipping address before selecting a shipping option. Dispatch setShippingOption action after shipping address selection.'
|
|
560
|
-
action: () => abortCheckout()
|
|
542
|
+
'You need to provide a shipping address before selecting a shipping option. Dispatch setShippingOption action after shipping address selection.'
|
|
561
543
|
}
|
|
562
544
|
]);
|
|
563
545
|
|
|
@@ -614,8 +596,7 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
614
596
|
method: 'POST',
|
|
615
597
|
body: {
|
|
616
598
|
payment_option: String(pk)
|
|
617
|
-
}
|
|
618
|
-
signal: getCheckoutAbortSignal()
|
|
599
|
+
}
|
|
619
600
|
}),
|
|
620
601
|
async onQueryStarted(arg, { dispatch, queryFulfilled, getState }) {
|
|
621
602
|
dispatch(setPaymentStepBusy(true));
|
|
@@ -624,7 +605,7 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
624
605
|
dispatch(setSelectedBankAccountPk(null));
|
|
625
606
|
dispatch(setCardType(null));
|
|
626
607
|
|
|
627
|
-
const state =
|
|
608
|
+
const state = getState();
|
|
628
609
|
|
|
629
610
|
validateCheckoutState(state, [
|
|
630
611
|
{
|
|
@@ -634,8 +615,7 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
634
615
|
return !preOrder?.shipping_option?.pk;
|
|
635
616
|
},
|
|
636
617
|
errorMessage:
|
|
637
|
-
'You need to select a shipping option before choosing a payment method. Dispatch setPaymentOption action after shipping option selection.'
|
|
638
|
-
action: () => abortCheckout()
|
|
618
|
+
'You need to select a shipping option before choosing a payment method. Dispatch setPaymentOption action after shipping option selection.'
|
|
639
619
|
}
|
|
640
620
|
]);
|
|
641
621
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/next",
|
|
3
3
|
"description": "Core package for Project Zero Next",
|
|
4
|
-
"version": "2.0.59
|
|
4
|
+
"version": "2.0.59",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"set-cookie-parser": "2.6.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@akinon/eslint-plugin-projectzero": "2.0.59
|
|
39
|
+
"@akinon/eslint-plugin-projectzero": "2.0.59",
|
|
40
40
|
"@babel/core": "7.26.10",
|
|
41
41
|
"@babel/preset-env": "7.26.9",
|
|
42
42
|
"@babel/preset-typescript": "7.27.0",
|
|
@@ -14,7 +14,7 @@ export const setAddressMiddleware: Middleware = ({
|
|
|
14
14
|
return result;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const { addressList: addresses } = getState().checkout;
|
|
17
|
+
const { addressList: addresses, deliveryOptions } = getState().checkout;
|
|
18
18
|
const { endpoints: apiEndpoints } = checkoutApi;
|
|
19
19
|
|
|
20
20
|
const hasNoAddresses =
|
|
@@ -31,6 +31,14 @@ export const setAddressMiddleware: Middleware = ({
|
|
|
31
31
|
isCustomerDelivery &&
|
|
32
32
|
firstAddressPk
|
|
33
33
|
) {
|
|
34
|
+
// Commerce requires a delivery option on the pre_order before addresses
|
|
35
|
+
// can be selected when delivery options are offered. Returning null
|
|
36
|
+
// keeps the pre-fix suppression of outer middlewares for this action
|
|
37
|
+
// (ZERO-4638).
|
|
38
|
+
if (deliveryOptions?.length > 0 && !preOrder.delivery_option?.pk) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
dispatch(
|
|
35
43
|
apiEndpoints.setAddresses.initiate({
|
|
36
44
|
shippingAddressPk: firstAddressPk,
|
|
@@ -23,6 +23,13 @@ export const deliveryOptionMiddleware: Middleware = ({
|
|
|
23
23
|
)?.pk;
|
|
24
24
|
|
|
25
25
|
if (customerDeliveryOption) {
|
|
26
|
+
// A delivery option cannot be selected for an empty basket. Returning
|
|
27
|
+
// null keeps the pre-fix suppression of outer middlewares for this
|
|
28
|
+
// action (ZERO-4638).
|
|
29
|
+
if (preOrder.basket?.basketitem_set?.length === 0) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
dispatch(
|
|
27
34
|
apiEndpoints.setDeliveryOption.initiate(customerDeliveryOption)
|
|
28
35
|
);
|
|
@@ -18,6 +18,13 @@ export const paymentOptionMiddleware: Middleware = ({
|
|
|
18
18
|
const { endpoints: apiEndpoints } = checkoutApi;
|
|
19
19
|
|
|
20
20
|
if (!preOrder?.payment_option && paymentOptions.length > 0) {
|
|
21
|
+
// Commerce requires a shipping option on the pre_order before a payment
|
|
22
|
+
// option can be selected. Returning null keeps the pre-fix suppression
|
|
23
|
+
// of outer middlewares for this action (ZERO-4638).
|
|
24
|
+
if (!preOrder.shipping_option?.pk) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
const firstPaymentOptionPk = paymentOptions[0]?.pk;
|
|
22
29
|
|
|
23
30
|
dispatch(apiEndpoints.setPaymentOption.initiate(firstPaymentOptionPk));
|
|
@@ -27,6 +27,16 @@ export const shippingOptionMiddleware: Middleware = ({
|
|
|
27
27
|
const defaultShippingOption = shippingOptions[0]?.pk;
|
|
28
28
|
|
|
29
29
|
if (defaultShippingOption) {
|
|
30
|
+
// Commerce requires both addresses on the pre_order before a shipping
|
|
31
|
+
// option can be selected; auto-dispatching earlier (e.g. from the
|
|
32
|
+
// early Checkout refetch during a guest's first address save) races
|
|
33
|
+
// the in-flight AddressSelectionPage request. Returning null keeps
|
|
34
|
+
// the pre-fix suppression of outer middlewares for this action
|
|
35
|
+
// (ZERO-4638).
|
|
36
|
+
if (!preOrder.billing_address || !preOrder.shipping_address) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
dispatch(
|
|
31
41
|
apiEndpoints.setShippingOption.initiate(defaultShippingOption)
|
|
32
42
|
);
|