@autobusal/providers 1.23.0 → 1.26.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/CHANGELOG.md +31 -0
- package/Setup/commerce.ts +138 -0
- package/package.json +1 -1
- package/types/routes.ts +13 -0
- package/types/settings.ts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.26.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `FoundData.arrival_offset` — whole days between boarding and arriving, for
|
|
8
|
+
the overnight marker on result cards. Optional: external-provider offers do
|
|
9
|
+
not report one, and a missing value must read as same-day rather than
|
|
10
|
+
invent a day.
|
|
11
|
+
|
|
12
|
+
## 1.25.0
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `LabelSettings.whatsapp.alert_template_name` / `alert_template_language` —
|
|
17
|
+
the separate approved template the route+date broadcast composer sends
|
|
18
|
+
through. Optional: a brand can confirm purchases over WhatsApp without ever
|
|
19
|
+
broadcasting, and Meta approves a template for a stated purpose, so service
|
|
20
|
+
alerts cannot reuse the confirmation one.
|
|
21
|
+
|
|
22
|
+
## 1.24.0
|
|
23
|
+
|
|
24
|
+
**New `Setup/commerce.ts` — the booking funnel, pushed to the dataLayer.**
|
|
25
|
+
|
|
26
|
+
`analytics.ts` already loaded the container and announced page views; this is what makes those page views mean something. Until now the funnel was unmeasurable for a more basic reason than missing events — every step of the wizard shared one URL, so no tool could tell a search from a checkout.
|
|
27
|
+
|
|
28
|
+
Uses **GA4's own event names** (`view_item_list`, `select_item`, `begin_checkout`, `add_to_cart`, `remove_from_cart`, `purchase`) so a container wires them by picking from a list rather than needing a custom mapping per brand.
|
|
29
|
+
|
|
30
|
+
`purchase` is the hard one and the reason this is a module rather than a few inline pushes: a card payment leaves the site entirely and returns on a URL carrying nothing but an order hash, and most buyers are guests with no account to read the order back from. So `stash()` remembers the value at order time and `flush()` announces it on return — clearing as it fires, and checking the hash, so neither a refresh nor a stale entry from an abandoned attempt can report a sale.
|
|
31
|
+
|
|
32
|
+
Each push clears `ecommerce` first; GTM's dataLayer is cumulative, and without that a later event inherits the previous one's items.
|
|
33
|
+
|
|
3
34
|
## 1.23.0
|
|
4
35
|
|
|
5
36
|
- `CountryData.timezone` — the IANA zone, so the date picker can floor itself on the departure city's date rather than ours or the buyer's. Carried on `/api/cities/browse`, which the search form already loads.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The booking funnel, pushed to the dataLayer.
|
|
3
|
+
*
|
|
4
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
5
|
+
*
|
|
6
|
+
* `analytics.ts` loads the container and announces page views; this is what
|
|
7
|
+
* makes those page views mean something. Until now the funnel was
|
|
8
|
+
* unmeasurable for a more basic reason than missing events - every step of
|
|
9
|
+
* the wizard shared one URL, so no tool could tell a search from a checkout.
|
|
10
|
+
* That is fixed; this is the other half.
|
|
11
|
+
*
|
|
12
|
+
* GA4'S OWN EVENT NAMES, deliberately. `view_item_list`, `select_item`,
|
|
13
|
+
* `begin_checkout`, `add_to_cart` and `purchase` are wired in GTM by
|
|
14
|
+
* choosing them from a list; anything invented here would need a custom
|
|
15
|
+
* mapping in every brand's container, and nothing in this codebase should
|
|
16
|
+
* know which tags a brand runs.
|
|
17
|
+
*
|
|
18
|
+
* PURCHASE IS THE HARD ONE, and the reason this file exists rather than a
|
|
19
|
+
* few inline pushes. A card payment leaves the site entirely and comes back
|
|
20
|
+
* on a URL carrying nothing but an order hash, and most buyers are guests
|
|
21
|
+
* with no account to look the order up with. So the value is stashed when
|
|
22
|
+
* the order is placed and flushed when the buyer returns - see stash() and
|
|
23
|
+
* flush() below.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const push = (payload: Record<string, unknown>): void => {
|
|
27
|
+
window.dataLayer = window.dataLayer || [];
|
|
28
|
+
|
|
29
|
+
// Clear the previous event's ecommerce object first. GTM's dataLayer is
|
|
30
|
+
// cumulative, so without this a later event inherits whatever items the
|
|
31
|
+
// last one left behind - the classic double-counted purchase.
|
|
32
|
+
window.dataLayer.push({ ecommerce: null });
|
|
33
|
+
window.dataLayer.push(payload);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export interface CommerceItem {
|
|
37
|
+
item_id: string
|
|
38
|
+
item_name: string
|
|
39
|
+
item_brand?: string
|
|
40
|
+
price?: number
|
|
41
|
+
quantity?: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Search results were shown. */
|
|
45
|
+
export const viewItemList = (items: CommerceItem[], currency?: string): void => {
|
|
46
|
+
if (items.length === 0) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
push({ event: 'view_item_list', ecommerce: { currency, items } });
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** One coach was chosen from the results. */
|
|
54
|
+
export const selectItem = (item: CommerceItem, currency?: string): void => {
|
|
55
|
+
push({ event: 'select_item', ecommerce: { currency, items: [item] } });
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** The buyer reached the passenger-details and payment screen. */
|
|
59
|
+
export const beginCheckout = (value: number, items: CommerceItem[], currency?: string): void => {
|
|
60
|
+
push({ event: 'begin_checkout', ecommerce: { currency, value, items } });
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A paid add-on was ticked or unticked.
|
|
65
|
+
*
|
|
66
|
+
* The attach rate this measures is the single input the Flexible Ticket
|
|
67
|
+
* pricing decision is waiting on - it cannot be banded by window without
|
|
68
|
+
* knowing how often each window is actually bought.
|
|
69
|
+
*/
|
|
70
|
+
export const addon = (added: boolean, name: string, price: number, currency?: string): void => {
|
|
71
|
+
push({
|
|
72
|
+
event: added ? 'add_to_cart' : 'remove_from_cart',
|
|
73
|
+
ecommerce: {
|
|
74
|
+
currency,
|
|
75
|
+
value: price,
|
|
76
|
+
items: [{ item_id: name, item_name: name, price, quantity: 1 }]
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const KEY = 'commerce.purchase';
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Remember what an order was worth, against its hash.
|
|
85
|
+
*
|
|
86
|
+
* sessionStorage rather than a variable: a card payment navigates away to
|
|
87
|
+
* the bank and back, so anything held in memory is gone by the time the
|
|
88
|
+
* buyer returns. Same origin either side, so the value survives.
|
|
89
|
+
*/
|
|
90
|
+
export const stash = (hash: string, value: number, items: CommerceItem[], currency?: string): void => {
|
|
91
|
+
try {
|
|
92
|
+
sessionStorage.setItem(KEY, JSON.stringify({ hash, value, items, currency }));
|
|
93
|
+
} catch {
|
|
94
|
+
// storage disabled or full - measurement is never worth breaking a sale
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Announce a completed purchase, once.
|
|
100
|
+
*
|
|
101
|
+
* CLEARED AS IT FIRES. The confirmation page is a plain URL a buyer can
|
|
102
|
+
* refresh, bookmark or reach twice from their history, and each of those
|
|
103
|
+
* would otherwise report another sale. The hash is checked too, so a stale
|
|
104
|
+
* entry from an abandoned attempt cannot be attributed to a later order.
|
|
105
|
+
*/
|
|
106
|
+
export const flush = (hash: string): void => {
|
|
107
|
+
let stored: { hash?: string, value?: number, items?: CommerceItem[], currency?: string } | null = null;
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const raw = sessionStorage.getItem(KEY);
|
|
111
|
+
|
|
112
|
+
stored = raw ? JSON.parse(raw) : null;
|
|
113
|
+
} catch {
|
|
114
|
+
stored = null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!stored || stored.hash !== hash) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
sessionStorage.removeItem(KEY);
|
|
123
|
+
} catch {
|
|
124
|
+
// if it cannot be cleared, do not fire - a repeatable purchase event is
|
|
125
|
+
// worse than a missing one
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
push({
|
|
130
|
+
event: 'purchase',
|
|
131
|
+
ecommerce: {
|
|
132
|
+
transaction_id: hash,
|
|
133
|
+
currency: stored.currency,
|
|
134
|
+
value: stored.value,
|
|
135
|
+
items: stored.items ?? []
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
};
|
package/package.json
CHANGED
package/types/routes.ts
CHANGED
|
@@ -146,6 +146,19 @@ export interface FoundData extends Omit<RouteData, 'locations' | 'id'> {
|
|
|
146
146
|
// another timezone, and "leaves in 20 minutes" must not be wrong.
|
|
147
147
|
departs_in?: number | null
|
|
148
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
151
|
+
* Whole days between boarding and arriving - 0 for a trip that arrives the
|
|
152
|
+
* same day, 1 for one arriving the next morning. Counted server-side from
|
|
153
|
+
* the timetable's own midnight markers, never guessed from the two clock
|
|
154
|
+
* times: a coach leaving 23:00 and arriving 07:00 is indistinguishable
|
|
155
|
+
* from one running backwards unless the timetable says which.
|
|
156
|
+
*
|
|
157
|
+
* Optional because external-provider offers do not report one; the card
|
|
158
|
+
* must read a missing value as same-day rather than invent a day.
|
|
159
|
+
*/
|
|
160
|
+
arrival_offset?: number
|
|
161
|
+
|
|
149
162
|
// The operator's subscription priority, as obtapi already sends it.
|
|
150
163
|
// Used only as a tiebreak in the "recommended" ordering - see
|
|
151
164
|
// routes-order's Found/refine.ts. Absent on external-provider offers.
|
package/types/settings.ts
CHANGED
|
@@ -217,6 +217,16 @@ export interface LabelSettings {
|
|
|
217
217
|
access_token?: string
|
|
218
218
|
template_name?: string
|
|
219
219
|
template_language?: string
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
223
|
+
* The separate approved template used by the route+date broadcast
|
|
224
|
+
* composer. Optional: a brand can confirm purchases without ever
|
|
225
|
+
* broadcasting, and Meta approves a template for a stated purpose, so
|
|
226
|
+
* service alerts cannot reuse the confirmation one.
|
|
227
|
+
*/
|
|
228
|
+
alert_template_name?: string
|
|
229
|
+
alert_template_language?: string
|
|
220
230
|
display_phone?: string
|
|
221
231
|
}
|
|
222
232
|
|