@autobusal/order-process 1.3.8 → 1.5.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/BKT/Bkt.tsx +12 -3
- package/Mollie/Mollie.tsx +27 -10
- package/Nexi/Nexi.tsx +27 -10
- package/Reiffeisen/Raiffeisen.tsx +27 -13
- package/Stripe/Stripe.tsx +27 -10
- package/package.json +5 -2
- package/services.ts +35 -0
- package/CHANGELOG.md +0 -61
package/BKT/Bkt.tsx
CHANGED
|
@@ -17,7 +17,7 @@ const Bkt = ({ hash, t }: Props): JSX.Element => {
|
|
|
17
17
|
|
|
18
18
|
const ref = useRef<HTMLFormElement>(null);
|
|
19
19
|
|
|
20
|
-
const { data, isLoading, isSuccess, isError } = useMakeBkt(String(hash));
|
|
20
|
+
const { data, isLoading, isSuccess, isError, error } = useMakeBkt(String(hash));
|
|
21
21
|
|
|
22
22
|
const navigate = useNavigate();
|
|
23
23
|
|
|
@@ -37,11 +37,20 @@ const Bkt = ({ hash, t }: Props): JSX.Element => {
|
|
|
37
37
|
// loading text forever with no way out. Fix: navigate back so they can
|
|
38
38
|
// retry. Same fix as Raiffeisen/Stripe/Mollie/Nexi (see Raiffeisen.tsx for
|
|
39
39
|
// the full explanation, including why there's no toast() call here).
|
|
40
|
+
/*
|
|
41
|
+
* See Raiffeisen for the reasoning - a refused payment goes to the
|
|
42
|
+
* booking's own payment-not-completed page, not back to a checkout form
|
|
43
|
+
* whose contents this navigation has already destroyed.
|
|
44
|
+
*/
|
|
40
45
|
useEffect(() => {
|
|
41
46
|
if (isError) {
|
|
42
|
-
|
|
47
|
+
const message = (error as { response?: { data?: { message?: string } } })?.response?.data?.message;
|
|
48
|
+
|
|
49
|
+
const reason = message ?? t('order_process.error', { ns: 'common', defaultValue: 'We could not complete your payment. Please try again.' });
|
|
50
|
+
|
|
51
|
+
navigate(hash ? `/orders/cancelled/${ hash }?msg=${ encodeURIComponent(reason) }` : '/', { replace: true });
|
|
43
52
|
}
|
|
44
|
-
}, [isError, navigate]);
|
|
53
|
+
}, [isError, error, hash, navigate, t]);
|
|
45
54
|
|
|
46
55
|
if (isLoading) {
|
|
47
56
|
return (
|
package/Mollie/Mollie.tsx
CHANGED
|
@@ -11,7 +11,7 @@ interface Props {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const Mollie = ({ hash, t }: Props): JSX.Element => {
|
|
14
|
-
const { data, isSuccess, isError } = useMakeMollie(String(hash));
|
|
14
|
+
const { data, isSuccess, isError, error } = useMakeMollie(String(hash));
|
|
15
15
|
|
|
16
16
|
const navigate = useNavigate();
|
|
17
17
|
|
|
@@ -21,18 +21,35 @@ const Mollie = ({ hash, t }: Props): JSX.Element => {
|
|
|
21
21
|
}
|
|
22
22
|
}, [data, isSuccess]);
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
/*
|
|
25
|
+
* A REFUSED CARD PAYMENT IS NOT A "GO BACK".
|
|
26
|
+
*
|
|
27
|
+
* Claude - 2026-08-31, reported by Ferjolt: "when I click on Pay button, it
|
|
28
|
+
* just refreshes and shows me a toaster error ... The toaster error is very
|
|
29
|
+
* easy to be missed and the visitor does not understand anything what
|
|
30
|
+
* happened."
|
|
31
|
+
*
|
|
32
|
+
* That is exactly what navigate(-1) looked like. It returned to the
|
|
33
|
+
* checkout URL, but the checkout's CONTENTS - the coach, the passengers,
|
|
34
|
+
* the seats - live in the wizard's React state, which navigating away had
|
|
35
|
+
* already destroyed. So the buyer landed back on an empty form, with the
|
|
36
|
+
* only explanation in a toast that fades.
|
|
37
|
+
*
|
|
38
|
+
* The order EXISTS at this point and its seats are still held; what failed
|
|
39
|
+
* is the settlement. So this goes where the funds path has gone since
|
|
40
|
+
* BOOK-11-b: the booking's own payment-not-completed page, which says the
|
|
41
|
+
* reservation is still theirs, carries the reason, and offers a retry.
|
|
42
|
+
* `replace`, because a failed attempt is not a step to press Back into.
|
|
43
|
+
*/
|
|
31
44
|
useEffect(() => {
|
|
32
45
|
if (isError) {
|
|
33
|
-
|
|
46
|
+
const message = (error as { response?: { data?: { message?: string } } })?.response?.data?.message;
|
|
47
|
+
|
|
48
|
+
const reason = message ?? t('order_process.error', { ns: 'common', defaultValue: 'We could not complete your payment. Please try again.' });
|
|
49
|
+
|
|
50
|
+
navigate(hash ? `/orders/cancelled/${ hash }?msg=${ encodeURIComponent(reason) }` : '/', { replace: true });
|
|
34
51
|
}
|
|
35
|
-
}, [isError, navigate]);
|
|
52
|
+
}, [isError, error, hash, navigate, t]);
|
|
36
53
|
|
|
37
54
|
return (
|
|
38
55
|
<Container>
|
package/Nexi/Nexi.tsx
CHANGED
|
@@ -11,7 +11,7 @@ interface Props {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const Nexi = ({ hash, t }: Props): JSX.Element => {
|
|
14
|
-
const { data, isSuccess, isError } = useMakeNexi(String(hash));
|
|
14
|
+
const { data, isSuccess, isError, error } = useMakeNexi(String(hash));
|
|
15
15
|
|
|
16
16
|
const navigate = useNavigate();
|
|
17
17
|
|
|
@@ -21,18 +21,35 @@ const Nexi = ({ hash, t }: Props): JSX.Element => {
|
|
|
21
21
|
}
|
|
22
22
|
}, [data, isSuccess]);
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
/*
|
|
25
|
+
* A REFUSED CARD PAYMENT IS NOT A "GO BACK".
|
|
26
|
+
*
|
|
27
|
+
* Claude - 2026-08-31, reported by Ferjolt: "when I click on Pay button, it
|
|
28
|
+
* just refreshes and shows me a toaster error ... The toaster error is very
|
|
29
|
+
* easy to be missed and the visitor does not understand anything what
|
|
30
|
+
* happened."
|
|
31
|
+
*
|
|
32
|
+
* That is exactly what navigate(-1) looked like. It returned to the
|
|
33
|
+
* checkout URL, but the checkout's CONTENTS - the coach, the passengers,
|
|
34
|
+
* the seats - live in the wizard's React state, which navigating away had
|
|
35
|
+
* already destroyed. So the buyer landed back on an empty form, with the
|
|
36
|
+
* only explanation in a toast that fades.
|
|
37
|
+
*
|
|
38
|
+
* The order EXISTS at this point and its seats are still held; what failed
|
|
39
|
+
* is the settlement. So this goes where the funds path has gone since
|
|
40
|
+
* BOOK-11-b: the booking's own payment-not-completed page, which says the
|
|
41
|
+
* reservation is still theirs, carries the reason, and offers a retry.
|
|
42
|
+
* `replace`, because a failed attempt is not a step to press Back into.
|
|
43
|
+
*/
|
|
31
44
|
useEffect(() => {
|
|
32
45
|
if (isError) {
|
|
33
|
-
|
|
46
|
+
const message = (error as { response?: { data?: { message?: string } } })?.response?.data?.message;
|
|
47
|
+
|
|
48
|
+
const reason = message ?? t('order_process.error', { ns: 'common', defaultValue: 'We could not complete your payment. Please try again.' });
|
|
49
|
+
|
|
50
|
+
navigate(hash ? `/orders/cancelled/${ hash }?msg=${ encodeURIComponent(reason) }` : '/', { replace: true });
|
|
34
51
|
}
|
|
35
|
-
}, [isError, navigate]);
|
|
52
|
+
}, [isError, error, hash, navigate, t]);
|
|
36
53
|
|
|
37
54
|
return (
|
|
38
55
|
<Container>
|
|
@@ -11,7 +11,7 @@ interface Props {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const Raiffeisen = ({ hash, t }: Props): JSX.Element => {
|
|
14
|
-
const { data, isSuccess, isError } = useMakeRaiffeisen(String(hash));
|
|
14
|
+
const { data, isSuccess, isError, error } = useMakeRaiffeisen(String(hash));
|
|
15
15
|
|
|
16
16
|
const navigate = useNavigate();
|
|
17
17
|
|
|
@@ -21,21 +21,35 @@ const Raiffeisen = ({ hash, t }: Props): JSX.Element => {
|
|
|
21
21
|
}
|
|
22
22
|
}, [data, isSuccess]);
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
/*
|
|
25
|
+
* A REFUSED CARD PAYMENT IS NOT A "GO BACK".
|
|
26
|
+
*
|
|
27
|
+
* Claude - 2026-08-31, reported by Ferjolt: "when I click on Pay button, it
|
|
28
|
+
* just refreshes and shows me a toaster error ... The toaster error is very
|
|
29
|
+
* easy to be missed and the visitor does not understand anything what
|
|
30
|
+
* happened."
|
|
31
|
+
*
|
|
32
|
+
* That is exactly what navigate(-1) looked like. It returned to the
|
|
33
|
+
* checkout URL, but the checkout's CONTENTS - the coach, the passengers,
|
|
34
|
+
* the seats - live in the wizard's React state, which navigating away had
|
|
35
|
+
* already destroyed. So the buyer landed back on an empty form, with the
|
|
36
|
+
* only explanation in a toast that fades.
|
|
37
|
+
*
|
|
38
|
+
* The order EXISTS at this point and its seats are still held; what failed
|
|
39
|
+
* is the settlement. So this goes where the funds path has gone since
|
|
40
|
+
* BOOK-11-b: the booking's own payment-not-completed page, which says the
|
|
41
|
+
* reservation is still theirs, carries the reason, and offers a retry.
|
|
42
|
+
* `replace`, because a failed attempt is not a step to press Back into.
|
|
43
|
+
*/
|
|
34
44
|
useEffect(() => {
|
|
35
45
|
if (isError) {
|
|
36
|
-
|
|
46
|
+
const message = (error as { response?: { data?: { message?: string } } })?.response?.data?.message;
|
|
47
|
+
|
|
48
|
+
const reason = message ?? t('order_process.error', { ns: 'common', defaultValue: 'We could not complete your payment. Please try again.' });
|
|
49
|
+
|
|
50
|
+
navigate(hash ? `/orders/cancelled/${ hash }?msg=${ encodeURIComponent(reason) }` : '/', { replace: true });
|
|
37
51
|
}
|
|
38
|
-
}, [isError, navigate]);
|
|
52
|
+
}, [isError, error, hash, navigate, t]);
|
|
39
53
|
|
|
40
54
|
return (
|
|
41
55
|
<Container>
|
package/Stripe/Stripe.tsx
CHANGED
|
@@ -11,7 +11,7 @@ interface Props {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const Stripe = ({ hash, t }: Props): JSX.Element => {
|
|
14
|
-
const { data, isSuccess, isError } = useMakeStripe(String(hash));
|
|
14
|
+
const { data, isSuccess, isError, error } = useMakeStripe(String(hash));
|
|
15
15
|
|
|
16
16
|
const navigate = useNavigate();
|
|
17
17
|
|
|
@@ -21,18 +21,35 @@ const Stripe = ({ hash, t }: Props): JSX.Element => {
|
|
|
21
21
|
}
|
|
22
22
|
}, [data, isSuccess]);
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
/*
|
|
25
|
+
* A REFUSED CARD PAYMENT IS NOT A "GO BACK".
|
|
26
|
+
*
|
|
27
|
+
* Claude - 2026-08-31, reported by Ferjolt: "when I click on Pay button, it
|
|
28
|
+
* just refreshes and shows me a toaster error ... The toaster error is very
|
|
29
|
+
* easy to be missed and the visitor does not understand anything what
|
|
30
|
+
* happened."
|
|
31
|
+
*
|
|
32
|
+
* That is exactly what navigate(-1) looked like. It returned to the
|
|
33
|
+
* checkout URL, but the checkout's CONTENTS - the coach, the passengers,
|
|
34
|
+
* the seats - live in the wizard's React state, which navigating away had
|
|
35
|
+
* already destroyed. So the buyer landed back on an empty form, with the
|
|
36
|
+
* only explanation in a toast that fades.
|
|
37
|
+
*
|
|
38
|
+
* The order EXISTS at this point and its seats are still held; what failed
|
|
39
|
+
* is the settlement. So this goes where the funds path has gone since
|
|
40
|
+
* BOOK-11-b: the booking's own payment-not-completed page, which says the
|
|
41
|
+
* reservation is still theirs, carries the reason, and offers a retry.
|
|
42
|
+
* `replace`, because a failed attempt is not a step to press Back into.
|
|
43
|
+
*/
|
|
31
44
|
useEffect(() => {
|
|
32
45
|
if (isError) {
|
|
33
|
-
|
|
46
|
+
const message = (error as { response?: { data?: { message?: string } } })?.response?.data?.message;
|
|
47
|
+
|
|
48
|
+
const reason = message ?? t('order_process.error', { ns: 'common', defaultValue: 'We could not complete your payment. Please try again.' });
|
|
49
|
+
|
|
50
|
+
navigate(hash ? `/orders/cancelled/${ hash }?msg=${ encodeURIComponent(reason) }` : '/', { replace: true });
|
|
34
51
|
}
|
|
35
|
-
}, [isError, navigate]);
|
|
52
|
+
}, [isError, error, hash, navigate, t]);
|
|
36
53
|
|
|
37
54
|
return (
|
|
38
55
|
<Container>
|
package/package.json
CHANGED
package/services.ts
CHANGED
|
@@ -2,9 +2,39 @@ import { useQuery, UseQueryResult } from '@tanstack/react-query';
|
|
|
2
2
|
import { apiClient } from '@autobusal/providers';
|
|
3
3
|
import { BKTData, StripeData, MollieData, RaiffeisenData, NexiData } from './types';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Starting a payment must happen EXACTLY ONCE per attempt.
|
|
7
|
+
*
|
|
8
|
+
* Claude - 2026-08-31, after Ferjolt hit "Too Many Attempts" retrying a
|
|
9
|
+
* card payment.
|
|
10
|
+
*
|
|
11
|
+
* These are useQuery, and react-query's defaults are built for reads: it
|
|
12
|
+
* retries a failure three times, and refetches on window focus, on reconnect
|
|
13
|
+
* and on mount. So ONE failed payment fired four POSTs, and alt-tabbing away
|
|
14
|
+
* and back fired more.
|
|
15
|
+
*
|
|
16
|
+
* Two things wrong with that, and the rate limit is the milder one. Each
|
|
17
|
+
* call creates an ORDER ENTRY at the gateway, and RaiAccept requires
|
|
18
|
+
* merchantOrderReference to be unique per attempt - it rejects a repeat as
|
|
19
|
+
* error 1007 - so the silent retries were manufacturing duplicate gateway
|
|
20
|
+
* orders for a single click, and the visible failure could be the retry
|
|
21
|
+
* being refused rather than the original problem.
|
|
22
|
+
*
|
|
23
|
+
* A payment is not a read. It fires once, when the component mounts, and a
|
|
24
|
+
* failure is reported rather than quietly attempted again.
|
|
25
|
+
*/
|
|
26
|
+
const ONCE = {
|
|
27
|
+
retry: false,
|
|
28
|
+
refetchOnWindowFocus: false,
|
|
29
|
+
refetchOnReconnect: false,
|
|
30
|
+
refetchOnMount: false,
|
|
31
|
+
staleTime: Infinity
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
5
34
|
export const useMakeStandard = (type: string, hash: string): UseQueryResult<string> => (
|
|
6
35
|
useQuery({
|
|
7
36
|
queryKey: ['process-payment-standard', { type, hash }],
|
|
37
|
+
...ONCE,
|
|
8
38
|
queryFn: async () => (
|
|
9
39
|
await apiClient
|
|
10
40
|
.post('/api/payment/make', {
|
|
@@ -21,6 +51,7 @@ export const useMakeStandard = (type: string, hash: string): UseQueryResult<stri
|
|
|
21
51
|
export const useMakeBkt = (hash: string): UseQueryResult<BKTData> => (
|
|
22
52
|
useQuery({
|
|
23
53
|
queryKey: ['process-payment-bkt', { hash }],
|
|
54
|
+
...ONCE,
|
|
24
55
|
queryFn: async () => (
|
|
25
56
|
await apiClient
|
|
26
57
|
.post('/api/payment/bkt', { hash })
|
|
@@ -34,6 +65,7 @@ export const useMakeBkt = (hash: string): UseQueryResult<BKTData> => (
|
|
|
34
65
|
export const useMakeStripe = (hash: string): UseQueryResult<StripeData> => (
|
|
35
66
|
useQuery({
|
|
36
67
|
queryKey: ['process-payment-stripe', { hash }],
|
|
68
|
+
...ONCE,
|
|
37
69
|
queryFn: async () => (
|
|
38
70
|
await apiClient
|
|
39
71
|
.post('/api/payment/stripe', { hash })
|
|
@@ -47,6 +79,7 @@ export const useMakeStripe = (hash: string): UseQueryResult<StripeData> => (
|
|
|
47
79
|
export const useMakeMollie = (hash: string): UseQueryResult<MollieData> => (
|
|
48
80
|
useQuery({
|
|
49
81
|
queryKey: ['process-payment-mollie', { hash }],
|
|
82
|
+
...ONCE,
|
|
50
83
|
queryFn: async () => (
|
|
51
84
|
await apiClient
|
|
52
85
|
.post('/api/payment/mollie', { hash })
|
|
@@ -60,6 +93,7 @@ export const useMakeMollie = (hash: string): UseQueryResult<MollieData> => (
|
|
|
60
93
|
export const useMakeRaiffeisen = (hash: string): UseQueryResult<RaiffeisenData> => (
|
|
61
94
|
useQuery({
|
|
62
95
|
queryKey: ['process-payment-raiffeisen', { hash }],
|
|
96
|
+
...ONCE,
|
|
63
97
|
queryFn: async () => (
|
|
64
98
|
await apiClient
|
|
65
99
|
.post('/api/payment/raiffeisen', { hash })
|
|
@@ -73,6 +107,7 @@ export const useMakeRaiffeisen = (hash: string): UseQueryResult<RaiffeisenData>
|
|
|
73
107
|
export const useMakeNexi = (hash: string): UseQueryResult<NexiData> => (
|
|
74
108
|
useQuery({
|
|
75
109
|
queryKey: ['process-payment-nexi', { hash }],
|
|
110
|
+
...ONCE,
|
|
76
111
|
queryFn: async () => (
|
|
77
112
|
await apiClient
|
|
78
113
|
.post('/api/payment/nexi', { hash })
|
package/CHANGELOG.md
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 1.3.8 - 2026-08-29
|
|
4
|
-
|
|
5
|
-
- A refused funds/cash settlement lands on the booking's own payment-not-completed page instead of navigate(-1) (audit A9-39). Back returned to the checkout URL while the checkout's contents lived in wizard state the navigation had destroyed, so the seller arrived at a bare results page with their held reservation findable only through Browse Orders.
|
|
6
|
-
|
|
7
|
-
## 1.3.6
|
|
8
|
-
|
|
9
|
-
### Fixed
|
|
10
|
-
|
|
11
|
-
- **A 200 with an empty body no longer navigates to the homepage** while the order sits reserved - the confirmation path is only followed when the API actually named one. (Audit BOOK-04-b.)
|
|
12
|
-
|
|
13
|
-
## 1.3.5
|
|
14
|
-
|
|
15
|
-
### Fixed
|
|
16
|
-
|
|
17
|
-
- **A refused funds payment spun forever.** The standard (funds/reserve) processor had only a success branch, so an agent's "not enough funds" left the page on "Processing your order" with no message and the order stuck reserved. It now surfaces the API's own message and returns the buyer so they can try again. (Platform audit BOOK-05-c.)
|
|
18
|
-
|
|
19
|
-
All notable changes to `@autobusal/order-process` are documented here. This
|
|
20
|
-
project adheres to [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
21
|
-
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
22
|
-
|
|
23
|
-
## [1.3.4] - 2026-07-30
|
|
24
|
-
|
|
25
|
-
### Fixed
|
|
26
|
-
|
|
27
|
-
- **`noIndex` never used on the order processing page (`OrderProcess.tsx`).**
|
|
28
|
-
Same rationale as order-confirm - a transient, hash-gated page with no
|
|
29
|
-
search value, previously indexable by default.
|
|
30
|
-
|
|
31
|
-
## [1.3.3] - 2026-07-24
|
|
32
|
-
|
|
33
|
-
### Fixed
|
|
34
|
-
|
|
35
|
-
- **`BKT/Bkt.tsx` was submitting a nearly-blank form to the bank.** The component reads camelCase fields (`clientId`, `orderId`, `fullName`, `postalCode`, etc.) matching the `BKTData` type, but `BKTController::make()` on the backend was returning a differently-shaped payload (raw bank field names like `clientid`, `ObtBktId`, `ShippingNameSurname`) with none of the expected keys present — every hidden input except `amount`/`description`/`rnd`/`hash`/`refreshtime` was silently `undefined`. Fixed on the obtapi side (`Prepare::$data` is now what's returned); no change needed here beyond adding the new `url` field.
|
|
36
|
-
- The gateway URL was hardcoded (`https://pgw.bkt.com.al/fim/est3Dgate`) instead of read from the label's own configured BKT credentials. Now reads `data.url`, which the admin GUI already has an editable field for.
|
|
37
|
-
|
|
38
|
-
## [1.3.2] - 2026-07-19
|
|
39
|
-
|
|
40
|
-
### Fixed
|
|
41
|
-
|
|
42
|
-
- **Payment-initiation failures no longer strand the user on a spinner.** The
|
|
43
|
-
five gateway components — `Reiffeisen/Raiffeisen.tsx`, `Stripe/Stripe.tsx`,
|
|
44
|
-
`Mollie/Mollie.tsx`, `Nexi/Nexi.tsx`, and `BKT/Bkt.tsx` — previously only
|
|
45
|
-
reacted to `isSuccess`. When the `useMake*` payment-initiation request
|
|
46
|
-
failed (gateway auth failure, no funds, etc.), the component sat on
|
|
47
|
-
"Preparing to take you to the payment portal…" indefinitely with no feedback
|
|
48
|
-
and no way out except leaving the page. Each now also consumes `isError`
|
|
49
|
-
from its query and, in an effect, calls `navigate(-1)` to return the user so
|
|
50
|
-
they can retry. No local `toast()`/`error()` is raised on purpose: the
|
|
51
|
-
`@autobusal/providers` Axios response interceptor already surfaces one toast
|
|
52
|
-
per 422/500, and adding another here stacked a duplicate.
|
|
53
|
-
- **BKT loading state could never resolve on failure (`BKT/Bkt.tsx`).** Unlike
|
|
54
|
-
the other four gateways, BKT never reached an `isSuccess` state on failure —
|
|
55
|
-
`isLoading` simply stayed `true` — so it was stuck on the loading text
|
|
56
|
-
forever. The new `isError` → `navigate(-1)` handling gives it the same exit
|
|
57
|
-
path as the others.
|
|
58
|
-
|
|
59
|
-
Authored by Ferjolt Ozuni. Consolidated from the magus and alvavel whitelabel
|
|
60
|
-
patch sets into canonical @autobusal source (eliminates per-repo patch-package
|
|
61
|
-
divergence).
|