@autobusal/order-confirm 1.2.0 → 1.2.2
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 +51 -3
- package/package.json +1 -1
- package/services.ts +43 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.2 - 2026-08-29
|
|
4
|
+
|
|
5
|
+
- The purchase confirmation shows the real PNR rather than the internal hash from its own URL (audit A3-03h) - the email, the ticket and the invoice all name the PNR, and it is the code /viewtrip and a support desk accept. Falls back to the hash, so the page can always confirm.
|
|
6
|
+
|
|
7
|
+
## 1.2.1
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **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.)
|
|
12
|
+
|
|
3
13
|
## 1.2.0
|
|
4
14
|
|
|
5
15
|
- 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.
|
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,7 @@ 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, useGetOrderNumber } from './services';
|
|
12
13
|
import { flush } from '@autobusal/providers/Setup/commerce';
|
|
13
14
|
|
|
14
15
|
interface Props {
|
|
@@ -39,6 +40,34 @@ const OrderConfirm = ({ t }: Props): (JSX.Element | null) => {
|
|
|
39
40
|
|
|
40
41
|
const { data: UserData } = useUserStore();
|
|
41
42
|
|
|
43
|
+
const { data: NumberData } = useGetOrderNumber(hash);
|
|
44
|
+
|
|
45
|
+
const orderNumber = NumberData?.order_number ?? String(hash ?? '');
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
* Claude - 2026-08-22 (audit finding BOOK-11-b): this page is where the
|
|
49
|
+
* gateway's cancelUrl AND failUrl land, and it used to say "your order
|
|
50
|
+
* has been cancelled" while the reservation kept holding its seats for
|
|
51
|
+
* two more hours. Auto-cancelling here would be wrong too - a declined
|
|
52
|
+
* card lands on the same URL and the buyer may want to retry - so the
|
|
53
|
+
* copy now tells the truth (payment not completed, reservation still
|
|
54
|
+
* held) and the buyer chooses: retry through the order page, or release
|
|
55
|
+
* the hold for real with this.
|
|
56
|
+
*/
|
|
57
|
+
const [ released, setReleased ] = useState<boolean>(false);
|
|
58
|
+
|
|
59
|
+
const { mutate: Release, isPending: releasing } = useReleaseOrder();
|
|
60
|
+
|
|
61
|
+
const onRelease = (): void => {
|
|
62
|
+
if (!hash) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Release({ hash }, {
|
|
67
|
+
onSuccess: () => setReleased(true)
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
42
71
|
/**
|
|
43
72
|
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
44
73
|
*
|
|
@@ -106,8 +135,16 @@ const OrderConfirm = ({ t }: Props): (JSX.Element | null) => {
|
|
|
106
135
|
{ t(`order_confirm.title.${ type }`) }
|
|
107
136
|
</Title>
|
|
108
137
|
|
|
138
|
+
{ /* Claude - 2026-08-29 (audit A3-03h, low): the booking's REAL order
|
|
139
|
+
number, not the URL hash. This page announced the 15-character
|
|
140
|
+
Hashids token as "your order with number ..." while the email, the
|
|
141
|
+
ticket and the invoice all named the six-character PNR - and the
|
|
142
|
+
PNR is what /viewtrip's own Order Number field accepts and what a
|
|
143
|
+
support desk can look up. Falls back to the hash while the lookup
|
|
144
|
+
is in flight or if it fails, which is exactly what was shown
|
|
145
|
+
before, so a confirmation page can still always confirm. */ }
|
|
109
146
|
<div className="box" dangerouslySetInnerHTML={{
|
|
110
|
-
__html: t(`order_confirm.content.${ type}`, { order: `<strong>${ escapeHtml(
|
|
147
|
+
__html: t(`order_confirm.content.${ type === 'cancelled' && released ? 'released' : type }`, { order: `<strong>${ escapeHtml(orderNumber) }</strong>` })
|
|
111
148
|
}} />
|
|
112
149
|
|
|
113
150
|
<>
|
|
@@ -118,11 +155,22 @@ const OrderConfirm = ({ t }: Props): (JSX.Element | null) => {
|
|
|
118
155
|
<Actions>
|
|
119
156
|
<Button
|
|
120
157
|
type="button"
|
|
121
|
-
text={ t('order_confirm.manage') }
|
|
158
|
+
text={ t(type === 'cancelled' ? 'order_confirm.retry' : 'order_confirm.manage') }
|
|
122
159
|
noMargin
|
|
123
160
|
onClick={ onManage }
|
|
124
161
|
/>
|
|
125
162
|
|
|
163
|
+
{ (type === 'cancelled' && !released) && (
|
|
164
|
+
<Button
|
|
165
|
+
type="button"
|
|
166
|
+
subtype="delete"
|
|
167
|
+
text={ t('order_confirm.release') }
|
|
168
|
+
loading={ releasing }
|
|
169
|
+
noMargin
|
|
170
|
+
onClick={ onRelease }
|
|
171
|
+
/>
|
|
172
|
+
) }
|
|
173
|
+
|
|
126
174
|
<Button
|
|
127
175
|
type="button"
|
|
128
176
|
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,45 @@ export const useGetOrderTelegramStatus = (hash: string): UseQueryResult<OrderTel
|
|
|
35
35
|
refetchIntervalInBackground: true
|
|
36
36
|
})
|
|
37
37
|
);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The booking's real Order Number, from the hash this page was given.
|
|
41
|
+
*
|
|
42
|
+
* Claude - 2026-08-29 (audit A3-03h, low). The confirmation had only the URL
|
|
43
|
+
* hash and printed it as "your order with number gnl3d5kolxpw78q", while the
|
|
44
|
+
* email, the ticket and the invoice for the same booking all said ZRQGV2 -
|
|
45
|
+
* and ZRQGV2 is the code the /viewtrip lookup and a support desk actually
|
|
46
|
+
* accept. Not retried and allowed to fail: a confirmation page must never
|
|
47
|
+
* fail to confirm, so the caller falls back to the hash.
|
|
48
|
+
*/
|
|
49
|
+
export const useGetOrderNumber = (hash?: string): UseQueryResult<{ order_number: string }> => (
|
|
50
|
+
useQuery({
|
|
51
|
+
queryKey: ['order-number', hash],
|
|
52
|
+
enabled: hash !== undefined && hash !== '',
|
|
53
|
+
retry: false,
|
|
54
|
+
queryFn: async () => (
|
|
55
|
+
await apiClient
|
|
56
|
+
.get('/api/orders/number', { params: { hash } })
|
|
57
|
+
.then(response => (
|
|
58
|
+
response.data
|
|
59
|
+
))
|
|
60
|
+
)
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
/*
|
|
65
|
+
* Claude - 2026-08-22 (audit finding BOOK-11-b): the "cancelled" return
|
|
66
|
+
* page used to claim the order was cancelled while the reservation kept
|
|
67
|
+
* holding its seats for two more hours. The page now tells the truth and
|
|
68
|
+
* offers this - the real cancellation, through the same endpoint the order
|
|
69
|
+
* page uses, hash-authorised so a guest checkout can release its own hold.
|
|
70
|
+
*/
|
|
71
|
+
export const useReleaseOrder = (): UseMutationResult<void, Error, { hash: string }, unknown> => (
|
|
72
|
+
useMutation({
|
|
73
|
+
mutationFn: async ({ hash }: { hash: string }) => {
|
|
74
|
+
await apiClient.delete('/api/orders/cancel', {
|
|
75
|
+
data: { order_hash: hash, note: 'payment_cancelled' }
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
);
|