@autobusal/order-confirm 1.2.1 → 1.3.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/OrderConfirm.tsx +53 -5
- package/package.json +5 -2
- package/services.ts +36 -0
- package/CHANGELOG.md +0 -76
package/OrderConfirm.tsx
CHANGED
|
@@ -4,12 +4,12 @@ import { useSearchParams, useParams, useNavigate, Navigate } from 'react-router-
|
|
|
4
4
|
import { AiOutlineCheck } from 'react-icons/ai';
|
|
5
5
|
import { BiErrorAlt } from 'react-icons/bi';
|
|
6
6
|
import { IoReturnUpBackOutline } from 'react-icons/io5';
|
|
7
|
-
import { Meta, Button } from '@autobusal/common';
|
|
7
|
+
import { Meta, Button, LoadingParagraph } from '@autobusal/common';
|
|
8
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';
|
|
12
|
+
import { useReleaseOrder, useGetOrderNumber } from './services';
|
|
13
13
|
import { flush } from '@autobusal/providers/Setup/commerce';
|
|
14
14
|
|
|
15
15
|
interface Props {
|
|
@@ -25,6 +25,21 @@ const escapeHtml = (value: string): string => (
|
|
|
25
25
|
}[char] as string))
|
|
26
26
|
);
|
|
27
27
|
|
|
28
|
+
/*
|
|
29
|
+
* What stands in for the confirmation sentence until the booking reference
|
|
30
|
+
* has been fetched.
|
|
31
|
+
*
|
|
32
|
+
* The page's own <Title> already says "reserved" / "purchased", so the
|
|
33
|
+
* outcome is on screen the whole time - only the reference is waited for,
|
|
34
|
+
* and the two lines sit in the same `.box` the sentence will land in, so
|
|
35
|
+
* nothing shifts when it arrives.
|
|
36
|
+
*/
|
|
37
|
+
const Waiting = (): JSX.Element => (
|
|
38
|
+
<div className="box">
|
|
39
|
+
<LoadingParagraph count={ 2 } />
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
|
|
28
43
|
// long enough to read the confirmation and its order number, short enough
|
|
29
44
|
// that nobody wonders whether the page has finished
|
|
30
45
|
const REDIRECT_AFTER = 5000;
|
|
@@ -40,6 +55,24 @@ const OrderConfirm = ({ t }: Props): (JSX.Element | null) => {
|
|
|
40
55
|
|
|
41
56
|
const { data: UserData } = useUserStore();
|
|
42
57
|
|
|
58
|
+
const { data: NumberData, isPending: numberPending } = useGetOrderNumber(hash);
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
* Claude - 2026-08-31 (ruled by Ferjolt: "We should not show the internal
|
|
62
|
+
* hash anywhere but should show always the PNR").
|
|
63
|
+
*
|
|
64
|
+
* This used to fall back to `hash` when the lookup had not answered, which
|
|
65
|
+
* is how a live buyer came to see a 15-character Hashids token where the
|
|
66
|
+
* six-character booking reference belonged: the lookup was rate-limited
|
|
67
|
+
* during the "Too Many Attempts" episode, the fallback fired, and the page
|
|
68
|
+
* confidently printed an internal credential the visitor cannot type into
|
|
69
|
+
* "check your reservation" and cannot quote to anyone.
|
|
70
|
+
*
|
|
71
|
+
* The hash is no longer a fallback at all. It is not a booking reference,
|
|
72
|
+
* so it is never the answer to "what is my booking reference".
|
|
73
|
+
*/
|
|
74
|
+
const orderNumber = NumberData?.order_number ?? '';
|
|
75
|
+
|
|
43
76
|
/*
|
|
44
77
|
* Claude - 2026-08-22 (audit finding BOOK-11-b): this page is where the
|
|
45
78
|
* gateway's cancelUrl AND failUrl land, and it used to say "your order
|
|
@@ -131,9 +164,24 @@ const OrderConfirm = ({ t }: Props): (JSX.Element | null) => {
|
|
|
131
164
|
{ t(`order_confirm.title.${ type }`) }
|
|
132
165
|
</Title>
|
|
133
166
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
167
|
+
{ /* Claude - 2026-08-29 (audit A3-03h, low): the booking's REAL order
|
|
168
|
+
number, not the URL hash. This page announced the 15-character
|
|
169
|
+
Hashids token as "your order with number ..." while the email, the
|
|
170
|
+
ticket and the invoice all named the six-character PNR - and the
|
|
171
|
+
PNR is what /viewtrip's own Order Number field accepts and what a
|
|
172
|
+
support desk can look up.
|
|
173
|
+
|
|
174
|
+
2026-08-31: the sentence waits for the number rather than filling
|
|
175
|
+
the gap with the hash. It is one short GET on a page that then sits
|
|
176
|
+
for five seconds before redirecting, so the wait is not felt - and
|
|
177
|
+
announcing the wrong reference is worse than announcing it a moment
|
|
178
|
+
later. `order` is bolded because the visitor is expected to WRITE
|
|
179
|
+
IT DOWN; it is the only thing on the page they need to keep. */ }
|
|
180
|
+
{ numberPending ? <Waiting /> : (
|
|
181
|
+
<div className="box" dangerouslySetInnerHTML={{
|
|
182
|
+
__html: t(`order_confirm.content.${ type === 'cancelled' && released ? 'released' : type }`, { order: `<strong>${ escapeHtml(orderNumber) }</strong>` })
|
|
183
|
+
}} />
|
|
184
|
+
) }
|
|
137
185
|
|
|
138
186
|
<>
|
|
139
187
|
{ (type === 'cancelled' && message !== '') && <Reason message={ message } t={ t } /> }
|
package/package.json
CHANGED
package/services.ts
CHANGED
|
@@ -36,6 +36,42 @@ export const useGetOrderTelegramStatus = (hash: string): UseQueryResult<OrderTel
|
|
|
36
36
|
})
|
|
37
37
|
);
|
|
38
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
|
+
|
|
54
|
+
/*
|
|
55
|
+
* Claude - 2026-08-31: this is the one query on the page that MUST answer.
|
|
56
|
+
* It was `retry: false`, and a single rate-limited response was enough to
|
|
57
|
+
* lose the buyer their booking reference on the page that exists to give
|
|
58
|
+
* it to them - which is how a live buyer was shown the internal hash.
|
|
59
|
+
*
|
|
60
|
+
* Retrying is safe here in a way it is not for the payment calls in the
|
|
61
|
+
* same file: this is a read, it creates nothing, and repeating it cannot
|
|
62
|
+
* duplicate an order the way a repeated initiation could.
|
|
63
|
+
*/
|
|
64
|
+
retry: 3,
|
|
65
|
+
queryFn: async () => (
|
|
66
|
+
await apiClient
|
|
67
|
+
.get('/api/orders/number', { params: { hash } })
|
|
68
|
+
.then(response => (
|
|
69
|
+
response.data
|
|
70
|
+
))
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
|
|
39
75
|
/*
|
|
40
76
|
* Claude - 2026-08-22 (audit finding BOOK-11-b): the "cancelled" return
|
|
41
77
|
* page used to claim the order was cancelled while the reservation kept
|
package/CHANGELOG.md
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
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
|
-
|
|
13
|
-
All notable changes to this package are documented here. This project follows
|
|
14
|
-
[Keep a Changelog](https://keepachangelog.com/) and [Semantic Versioning](https://semver.org/).
|
|
15
|
-
|
|
16
|
-
## [1.1.3] - 2026-08-01
|
|
17
|
-
|
|
18
|
-
### Changed
|
|
19
|
-
|
|
20
|
-
- **The confirmation moves on to the order after five seconds**, where the
|
|
21
|
-
tickets and the invoice actually are. A dead-end success page turned
|
|
22
|
-
downloading the thing you had just bought into a hunt. Only for purchased
|
|
23
|
-
and reserved: a cancellation has nothing to go to, and bouncing someone
|
|
24
|
-
off a notice they may still be reading would be its own small insult.
|
|
25
|
-
Cleared on unmount, so pressing a button first does not leave a second
|
|
26
|
-
navigation queued behind the one you chose.
|
|
27
|
-
|
|
28
|
-
## [1.1.2] - 2026-07-30
|
|
29
|
-
|
|
30
|
-
### Fixed
|
|
31
|
-
|
|
32
|
-
- **`noIndex` never used on the order confirmation page (`OrderConfirm.tsx`).**
|
|
33
|
-
Reserved/purchased/cancelled order confirmations are reachable by anyone
|
|
34
|
-
with the emailed link, no login required, and each is single-use - there is
|
|
35
|
-
no canonical indexable version. `<Meta>`'s `noIndex` prop existed but was
|
|
36
|
-
never passed anywhere in the app; it now is here.
|
|
37
|
-
|
|
38
|
-
## [1.1.1] - 2026-07-25
|
|
39
|
-
|
|
40
|
-
### Fixed
|
|
41
|
-
|
|
42
|
-
- The "Connect Telegram" addon widget now also renders on `reserved` order
|
|
43
|
-
confirmations, not just `purchased` ones - a reserve-then-pay-later
|
|
44
|
-
booking has no other page offering the binding link, so it was a dead
|
|
45
|
-
end for the Telegram addon on that path.
|
|
46
|
-
|
|
47
|
-
## [1.1.0] - 2026-07-25
|
|
48
|
-
|
|
49
|
-
### Added
|
|
50
|
-
|
|
51
|
-
- **Post-purchase Telegram addon binding.** When a purchased order has a
|
|
52
|
-
Telegram notification addon still awaiting binding, the "purchased"
|
|
53
|
-
confirmation page now shows a "Connect Telegram" deep link (polls obtapi's
|
|
54
|
-
`GET /api/orders/telegram/status?hash=...` every 4s until bound). Mirrors
|
|
55
|
-
magus's account-level `AccountTelegram` binding flow, but order-scoped
|
|
56
|
-
since a guest checkout has no account to bind against.
|
|
57
|
-
|
|
58
|
-
## [1.0.1] - 2026-07-19
|
|
59
|
-
|
|
60
|
-
### Security
|
|
61
|
-
|
|
62
|
-
- `OrderConfirm.tsx`: fixed an HTML-injection (XSS) vector. The `hash` segment
|
|
63
|
-
comes straight from the URL (`/orders/:type/:hash`) and was interpolated into
|
|
64
|
-
markup rendered via `dangerouslySetInnerHTML`. Added an `escapeHtml` helper
|
|
65
|
-
that encodes `& < > " '` and applied it to the hash before interpolation
|
|
66
|
-
(`<strong>${ escapeHtml(String(hash ?? '')) }</strong>`), so a crafted hash
|
|
67
|
-
(e.g. containing `<img onerror=...>`) can no longer inject markup.
|
|
68
|
-
|
|
69
|
-
### Fixed
|
|
70
|
-
|
|
71
|
-
- `OrderConfirm.tsx`: the "Manage order" action now routes based on auth state.
|
|
72
|
-
A logged-in user is sent to `/account/orders` (where the order is listed)
|
|
73
|
-
instead of the public `/viewtrip/:hash` lookup form that email-gates access to
|
|
74
|
-
their own order; guests still use the public `/viewtrip/:hash` lookup.
|
|
75
|
-
|
|
76
|
-
Authored by Ferjolt Ozuni. Consolidated from the magus and alvavel whitelabel patch sets into canonical @autobusal source (eliminates per-repo patch-package divergence).
|