@autobusal/routes-order 1.33.2 → 1.33.4

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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.33.4
4
+
5
+ ### Added
6
+
7
+ - **Complimentary in the web checkout.** An operator/employee selling on their OWN operator's route is offered the zero-cost Complimentary option the mobile app already had - the route-aware flag comes from `/api/funds/allowed?route=...` and the ownership rule is re-checked server-side at payment time. (Audit decision BOOK-07; needs obtapi >= 2.4.37.)
8
+
9
+ ## 1.33.3
10
+
11
+ ### Fixed
12
+
13
+ - **Checkout swallowed every booking error.** A 4xx from order creation - a seat taken while the form was open, the unpaid-hold limit, a route gone unavailable - left the buyer on the filled form with no message. It now surfaces the API's message. (Audit BOOK-13-c.)
14
+
3
15
  ## 1.33.2
4
16
 
5
17
  ### Fixed
@@ -27,6 +27,7 @@ import { useGetSettings } from '@autobusal/providers/services';
27
27
  import { usePostOrder, useGetFlex, useGetPayments, useGetSavedPassengers } from '../services';
28
28
  import { addon as commerceAddon, beginCheckout, stash } from '@autobusal/providers/Setup/commerce';
29
29
  import { item as commerceItem } from '../commerce';
30
+ import { error as notifyError } from '@autobusal/utilities';
30
31
 
31
32
  interface Props {
32
33
  values?: PersonData
@@ -113,7 +114,7 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
113
114
  * answers it from cache - this reads the answer, it does not fetch it a
114
115
  * second time.
115
116
  */
116
- const { data: paymentsData } = useGetPayments();
117
+ const { data: paymentsData } = useGetPayments(step2.id);
117
118
 
118
119
  const singleMethod = getAvailablePayments(paymentsData, isPartner ?? false).length === 1;
119
120
 
@@ -351,6 +352,20 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
351
352
  );
352
353
 
353
354
  navigate(`/orders/process/${ action }/${ hash }`);
355
+ },
356
+ /*
357
+ * Edited: Claude - Date: 2026-08-22 (audit finding BOOK-13-c)
358
+ *
359
+ * Surface why the booking was refused. Every 4xx from order creation -
360
+ * a seat taken while the form was open, the unpaid-hold limit, a route
361
+ * that just went unavailable - left the buyer on the filled form with
362
+ * no message, clicking Pay again. The API's own message says exactly
363
+ * what happened.
364
+ */
365
+ onError: (submitError: unknown) => {
366
+ const message = (submitError as { response?: { data?: { message?: string } } })?.response?.data?.message;
367
+
368
+ notifyError(message ?? t('routes_order.checkout.error', { ns: 'common', defaultValue: 'We could not complete your booking. Please try again.' }));
354
369
  }
355
370
  });
356
371
  };
@@ -432,6 +447,7 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
432
447
  <Payments
433
448
  selected={ action ?? '' }
434
449
  isPartner={ isPartner ?? false }
450
+ routeId={ step2.id }
435
451
  t={ t }
436
452
  onChange={ onAction }
437
453
  />
@@ -1,7 +1,7 @@
1
1
  import { useEffect } from 'react';
2
2
  import { TFunction } from 'i18next';
3
3
  import { RiSecurePaymentLine } from 'react-icons/ri';
4
- import { FaRegHandPaper, FaMoneyBill } from 'react-icons/fa';
4
+ import { FaRegHandPaper, FaMoneyBill, FaGift } from 'react-icons/fa';
5
5
  import { AiOutlineCreditCard } from 'react-icons/ai';
6
6
  import { Inline } from '@autobusal/common';
7
7
  import { SubTitle } from '../../styles';
@@ -12,12 +12,13 @@ import { getAvailablePayments } from '../utilities';
12
12
  interface Props {
13
13
  selected: string
14
14
  isPartner: boolean
15
+ routeId?: number | string
15
16
  t: TFunction<'common'>
16
17
  onChange: (action: string) => void
17
18
  }
18
19
 
19
- const Payments = ({ selected, isPartner, t, onChange }: Props): JSX.Element | null => {
20
- const { data, isLoading, isSuccess } = useGetPayments();
20
+ const Payments = ({ selected, isPartner, routeId, t, onChange }: Props): JSX.Element | null => {
21
+ const { data, isLoading, isSuccess } = useGetPayments(routeId);
21
22
 
22
23
  const available = getAvailablePayments(data, isPartner);
23
24
 
@@ -90,6 +91,13 @@ const Payments = ({ selected, isPartner, t, onChange }: Props): JSX.Element | nu
90
91
  </ButtonPayment>
91
92
  ) }
92
93
 
94
+ { (data?.complimentary && !isPartner) && (
95
+ <ButtonPayment type="button" $selected={ selected === 'complimentary' } onClick={ () => onChange('complimentary') }>
96
+ <FaGift />
97
+ { t('routes_order.step5.actions.complimentary') }
98
+ </ButtonPayment>
99
+ ) }
100
+
93
101
  { (data?.bkt || isPartner) && (
94
102
  <ButtonPayment type="button" $selected={ selected === 'bkt' } onClick={ () => onChange('bkt') }>
95
103
  <AiOutlineCreditCard />
@@ -51,6 +51,11 @@ export const getAvailablePayments = (data?: ActionData, isPartner = false): stri
51
51
  if (data.reserve && !isPartner) { available.push('reserve'); }
52
52
  if (data.funds && !isPartner) { available.push('funds', 'cash'); }
53
53
 
54
+ // Claude - 2026-08-22 (audit decision BOOK-07): an operator/employee on
55
+ // their own operator's route pays nothing - the flag is route-aware
56
+ // (services.ts) and re-checked server-side at payment time.
57
+ if (data.complimentary && !isPartner) { available.push('complimentary'); }
58
+
54
59
  if (data.bkt || isPartner) { available.push('bkt'); }
55
60
  if (data.stripe || isPartner) { available.push('stripe'); }
56
61
  if (data.mollie || isPartner) { available.push('mollie'); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/routes-order",
3
- "version": "1.33.2",
3
+ "version": "1.33.4",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"
package/services.ts CHANGED
@@ -222,12 +222,20 @@ export const useGetCoupon = (routeId: number, total: number): UseMutationResult<
222
222
  })
223
223
  );
224
224
 
225
- export const useGetPayments = (): UseQueryResult<ActionData> => (
225
+ /*
226
+ * Claude - 2026-08-22 (audit decision BOOK-07): asks with the route being
227
+ * sold, so the API can offer Complimentary to an operator/employee on
228
+ * their own operator's route. External routes carry string ids and can
229
+ * never be own inventory, so those simply don't send the parameter.
230
+ */
231
+ export const useGetPayments = (routeId?: number | string): UseQueryResult<ActionData> => (
226
232
  useQuery({
227
- queryKey: ['allowed-funds'],
233
+ queryKey: ['allowed-funds', { routeId }],
228
234
  queryFn: async () => (
229
235
  await apiClient
230
- .get('/api/funds/allowed')
236
+ .get('/api/funds/allowed', {
237
+ params: typeof routeId === 'number' ? { route: routeId } : {}
238
+ })
231
239
  .then(response => (
232
240
  response.data
233
241
  ))
package/types.ts CHANGED
@@ -42,6 +42,7 @@ export interface ActionData {
42
42
  fake: boolean
43
43
  reserve: boolean
44
44
  funds: boolean
45
+ complimentary?: boolean
45
46
  bkt: boolean
46
47
  stripe: boolean
47
48
  mollie: boolean