@autobusal/order-confirm 1.1.3 → 1.2.1
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 +10 -0
- package/OrderConfirm.tsx +57 -3
- package/package.json +1 -1
- package/services.ts +18 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.1
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **The cancelled-payment page tells the truth.** It used to say "your order has been cancelled" while the reservation kept holding its seats for two more hours - and the gateway's failUrl lands on the same page, so auto-cancelling would kill declined-card retries. New copy (payment not completed, nothing charged, reservation still held), a Try-again action, and a real "Release my reservation" button through the hash-authorised cancel endpoint. (Audit BOOK-11-b.)
|
|
8
|
+
|
|
9
|
+
## 1.2.0
|
|
10
|
+
|
|
11
|
+
- Announces `purchase` on arrival, for a **purchased** order only — a reservation is not revenue and a cancelled attempt certainly is not. Cleared as it fires, so refreshing this page (a plain URL with the buyer's order number on it) cannot report the same sale twice.
|
|
12
|
+
|
|
3
13
|
All notable changes to this package are documented here. This project follows
|
|
4
14
|
[Keep a Changelog](https://keepachangelog.com/) and [Semantic Versioning](https://semver.org/).
|
|
5
15
|
|
package/OrderConfirm.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
import { TFunction } from 'i18next';
|
|
3
3
|
import { useSearchParams, useParams, useNavigate, Navigate } from 'react-router-dom';
|
|
4
4
|
import { AiOutlineCheck } from 'react-icons/ai';
|
|
@@ -9,6 +9,8 @@ import Reason from './Reason';
|
|
|
9
9
|
import Telegram from './Telegram/Telegram';
|
|
10
10
|
import { Title, Actions } from './styles';
|
|
11
11
|
import { useUserStore } from '@autobusal/providers/stores/user';
|
|
12
|
+
import { useReleaseOrder } from './services';
|
|
13
|
+
import { flush } from '@autobusal/providers/Setup/commerce';
|
|
12
14
|
|
|
13
15
|
interface Props {
|
|
14
16
|
t: TFunction<'common'>
|
|
@@ -38,6 +40,47 @@ const OrderConfirm = ({ t }: Props): (JSX.Element | null) => {
|
|
|
38
40
|
|
|
39
41
|
const { data: UserData } = useUserStore();
|
|
40
42
|
|
|
43
|
+
/*
|
|
44
|
+
* Claude - 2026-08-22 (audit finding BOOK-11-b): this page is where the
|
|
45
|
+
* gateway's cancelUrl AND failUrl land, and it used to say "your order
|
|
46
|
+
* has been cancelled" while the reservation kept holding its seats for
|
|
47
|
+
* two more hours. Auto-cancelling here would be wrong too - a declined
|
|
48
|
+
* card lands on the same URL and the buyer may want to retry - so the
|
|
49
|
+
* copy now tells the truth (payment not completed, reservation still
|
|
50
|
+
* held) and the buyer chooses: retry through the order page, or release
|
|
51
|
+
* the hold for real with this.
|
|
52
|
+
*/
|
|
53
|
+
const [ released, setReleased ] = useState<boolean>(false);
|
|
54
|
+
|
|
55
|
+
const { mutate: Release, isPending: releasing } = useReleaseOrder();
|
|
56
|
+
|
|
57
|
+
const onRelease = (): void => {
|
|
58
|
+
if (!hash) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Release({ hash }, {
|
|
63
|
+
onSuccess: () => setReleased(true)
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
69
|
+
*
|
|
70
|
+
* Only a PURCHASE counts. A reservation is not revenue and a cancelled
|
|
71
|
+
* attempt certainly is not, and reporting either as a sale is the kind of
|
|
72
|
+
* error that is never noticed because the number only ever looks good.
|
|
73
|
+
*
|
|
74
|
+
* flush() clears as it fires, so refreshing this page - which a buyer
|
|
75
|
+
* reasonably might, it is a plain URL with their order number on it -
|
|
76
|
+
* cannot report the same sale twice.
|
|
77
|
+
*/
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (type === 'purchased' && hash) {
|
|
80
|
+
flush(hash);
|
|
81
|
+
}
|
|
82
|
+
}, [type, hash]);
|
|
83
|
+
|
|
41
84
|
const isAllowed = ['reserved', 'cancelled', 'purchased'].includes(String(type));
|
|
42
85
|
|
|
43
86
|
if (!isAllowed) {
|
|
@@ -89,7 +132,7 @@ const OrderConfirm = ({ t }: Props): (JSX.Element | null) => {
|
|
|
89
132
|
</Title>
|
|
90
133
|
|
|
91
134
|
<div className="box" dangerouslySetInnerHTML={{
|
|
92
|
-
__html: t(`order_confirm.content.${ type}`, { order: `<strong>${ escapeHtml(String(hash ?? '')) }</strong>` })
|
|
135
|
+
__html: t(`order_confirm.content.${ type === 'cancelled' && released ? 'released' : type }`, { order: `<strong>${ escapeHtml(String(hash ?? '')) }</strong>` })
|
|
93
136
|
}} />
|
|
94
137
|
|
|
95
138
|
<>
|
|
@@ -100,11 +143,22 @@ const OrderConfirm = ({ t }: Props): (JSX.Element | null) => {
|
|
|
100
143
|
<Actions>
|
|
101
144
|
<Button
|
|
102
145
|
type="button"
|
|
103
|
-
text={ t('order_confirm.manage') }
|
|
146
|
+
text={ t(type === 'cancelled' ? 'order_confirm.retry' : 'order_confirm.manage') }
|
|
104
147
|
noMargin
|
|
105
148
|
onClick={ onManage }
|
|
106
149
|
/>
|
|
107
150
|
|
|
151
|
+
{ (type === 'cancelled' && !released) && (
|
|
152
|
+
<Button
|
|
153
|
+
type="button"
|
|
154
|
+
subtype="delete"
|
|
155
|
+
text={ t('order_confirm.release') }
|
|
156
|
+
loading={ releasing }
|
|
157
|
+
noMargin
|
|
158
|
+
onClick={ onRelease }
|
|
159
|
+
/>
|
|
160
|
+
) }
|
|
161
|
+
|
|
108
162
|
<Button
|
|
109
163
|
type="button"
|
|
110
164
|
subtype="secondary"
|
package/package.json
CHANGED
package/services.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useQuery, UseQueryResult } from '@tanstack/react-query';
|
|
1
|
+
import { useMutation, useQuery, UseMutationResult, UseQueryResult } from '@tanstack/react-query';
|
|
2
2
|
import { apiClient } from '@autobusal/providers';
|
|
3
3
|
|
|
4
4
|
export interface OrderTelegramStatus {
|
|
@@ -35,3 +35,20 @@ export const useGetOrderTelegramStatus = (hash: string): UseQueryResult<OrderTel
|
|
|
35
35
|
refetchIntervalInBackground: true
|
|
36
36
|
})
|
|
37
37
|
);
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
* Claude - 2026-08-22 (audit finding BOOK-11-b): the "cancelled" return
|
|
41
|
+
* page used to claim the order was cancelled while the reservation kept
|
|
42
|
+
* holding its seats for two more hours. The page now tells the truth and
|
|
43
|
+
* offers this - the real cancellation, through the same endpoint the order
|
|
44
|
+
* page uses, hash-authorised so a guest checkout can release its own hold.
|
|
45
|
+
*/
|
|
46
|
+
export const useReleaseOrder = (): UseMutationResult<void, Error, { hash: string }, unknown> => (
|
|
47
|
+
useMutation({
|
|
48
|
+
mutationFn: async ({ hash }: { hash: string }) => {
|
|
49
|
+
await apiClient.delete('/api/orders/cancel', {
|
|
50
|
+
data: { order_hash: hash, note: 'payment_cancelled' }
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
);
|