@autobusal/order-confirm 1.2.0 → 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 CHANGED
@@ -1,5 +1,11 @@
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
+
3
9
  ## 1.2.0
4
10
 
5
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.
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 } from './services';
12
13
  import { flush } from '@autobusal/providers/Setup/commerce';
13
14
 
14
15
  interface Props {
@@ -39,6 +40,30 @@ const OrderConfirm = ({ t }: Props): (JSX.Element | null) => {
39
40
 
40
41
  const { data: UserData } = useUserStore();
41
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
+
42
67
  /**
43
68
  * Edited: Ferjolt Ozuni - Date: 2026-08-05
44
69
  *
@@ -107,7 +132,7 @@ const OrderConfirm = ({ t }: Props): (JSX.Element | null) => {
107
132
  </Title>
108
133
 
109
134
  <div className="box" dangerouslySetInnerHTML={{
110
- __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>` })
111
136
  }} />
112
137
 
113
138
  <>
@@ -118,11 +143,22 @@ const OrderConfirm = ({ t }: Props): (JSX.Element | null) => {
118
143
  <Actions>
119
144
  <Button
120
145
  type="button"
121
- text={ t('order_confirm.manage') }
146
+ text={ t(type === 'cancelled' ? 'order_confirm.retry' : 'order_confirm.manage') }
122
147
  noMargin
123
148
  onClick={ onManage }
124
149
  />
125
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
+
126
162
  <Button
127
163
  type="button"
128
164
  subtype="secondary"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/order-confirm",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"
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
+ );