Skip to content

Commit 96fdc24

Browse files
Merge pull request #14 from peek-travel/feat/booking-source-details
feat: add sourceDetails to bookings from initialQuote actor name
2 parents 3394610 + cbd39fb commit 96fdc24

6 files changed

Lines changed: 17 additions & 4 deletions

File tree

docs/webhooks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ the full price breakdown, so one registration captures everything; the parser
4646
auto-detects whatever a given payload carries.
4747

4848
```
49-
{ displayId id primaryGuest { name email phone optinMarketing optinSms isGdpr postalCode } activitySnapshot { type name id } ticketQuantities { quantity resourceOptionSnapshot { name id } } reservationStatus checkinStatus returnStatus fulfillmentStatusOverride { status } timeSnapshot { id legacyId } purchasedAt purchasedAtUtc startsAt startsAtUtc endsAt endsAtUtc availabilityTimeId bookingPortalUrl operatorNotes value { convenienceFee { amount formatted } deposit { amount formatted } discount { amount formatted } discountedPrice { amount formatted } fees { amount formatted } flatPartnerFee { amount formatted } price { amount formatted } retailPrice { amount formatted } taxes { amount formatted } tips { amount formatted } total { formatted amount } } balance { total { amount formatted } } tips { price { amount formatted } } order { displayId id promoCodes { code } channelSnapshot { id name agent { name } } initialQuote { source { actor { app } } } } questionAnswers { answer questionText questionLocationSnapshot { latitude longitude } } tickets { questionAnswers { answer questionText } } resourcePoolAssignments { quantity resourcePool { name shortName resources { name } } resourceAssignments { resource { id name } } } bookingGuests { id name country dateOfBirth email isGdpr isParticipant optinSms optinMarketing phone postalCode fieldResponses { id text fieldLocation { field { name } } } } primaryGuest { id name country dateOfBirth email isGdpr isParticipant optinSms optinMarketing phone postalCode fieldResponses { id text fieldLocation { field { name } } } } }
49+
{ displayId id primaryGuest { name email phone optinMarketing optinSms isGdpr postalCode } activitySnapshot { type name id } ticketQuantities { quantity resourceOptionSnapshot { name id } } reservationStatus checkinStatus returnStatus fulfillmentStatusOverride { status } timeSnapshot { id legacyId } purchasedAt purchasedAtUtc startsAt startsAtUtc endsAt endsAtUtc availabilityTimeId bookingPortalUrl operatorNotes value { convenienceFee { amount formatted } deposit { amount formatted } discount { amount formatted } discountedPrice { amount formatted } fees { amount formatted } flatPartnerFee { amount formatted } price { amount formatted } retailPrice { amount formatted } taxes { amount formatted } tips { amount formatted } total { formatted amount } } balance { total { amount formatted } } tips { price { amount formatted } } order { displayId id promoCodes { code } channelSnapshot { id name agent { name } } initialQuote { source { actor { app name } } } } questionAnswers { answer questionText questionLocationSnapshot { latitude longitude } } tickets { questionAnswers { answer questionText } } resourcePoolAssignments { quantity resourcePool { name shortName resources { name } } resourceAssignments { resource { id name } } } bookingGuests { id name country dateOfBirth email isGdpr isParticipant optinSms optinMarketing phone postalCode fieldResponses { id text fieldLocation { field { name } } } } primaryGuest { id name country dateOfBirth email isGdpr isParticipant optinSms optinMarketing phone postalCode fieldResponses { id text fieldLocation { field { name } } } } }
5050
```
5151

5252
> This package owns this string internally and snapshots it in

src/internal/bookings/booking-converter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export function fromBookingNode(
6868
const data = node ?? {};
6969
const ticketQuantities = data.ticketQuantities ?? [];
7070
const app = data.order?.initialQuote?.source?.actor?.app ?? null;
71+
const sourceDetails = data.order?.initialQuote?.source?.actor?.name ?? null;
7172

7273
const customQuestionAnswers: CustomQuestionAnswer[] = (data.questionAnswers ?? []).map(
7374
(answer) => {
@@ -101,6 +102,7 @@ export function fromBookingNode(
101102
source: sourceFromApp(app),
102103
sourceApp: app || UNKNOWN,
103104
sourceDescription: sourceDescriptionFromApp(app),
105+
sourceDetails,
104106

105107
customerName: data.primaryGuest?.name || "",
106108
customerEmail: data.primaryGuest?.email || null,

src/internal/bookings/booking-queries.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export const bookingQueryFields = `
122122
source {
123123
actor {
124124
app
125+
name
125126
}
126127
}
127128
}
@@ -514,7 +515,9 @@ export interface BookingNode {
514515
id?: string;
515516
promoCodes?: Array<{ code: string }>;
516517
channelSnapshot?: { id?: string; name?: string; agent?: { name?: string } | null } | null;
517-
initialQuote?: { source?: { actor?: { app?: string } | null } | null } | null;
518+
initialQuote?: {
519+
source?: { actor?: { app?: string; name?: string } | null } | null;
520+
} | null;
518521
} | null;
519522
questionAnswers?: Array<{
520523
answer: string;

src/models/booking.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ export interface Booking {
139139
* `"unknown"` when the origin can't be mapped.
140140
*/
141141
sourceDescription: string;
142+
/**
143+
* Raw source actor name reported by Peek, when present. `null` when the
144+
* origin actor carries no name.
145+
*/
146+
sourceDetails: string | null;
142147

143148
/** Primary guest's name, or `""` when unknown. */
144149
customerName: string | null;

test/bookings/booking-converter.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function fullNode(): BookingNode {
6363
id: "o_1",
6464
promoCodes: [{ code: "SUMMER" }],
6565
channelSnapshot: { id: "ch-1", name: "Acme", agent: { name: "Jane" } },
66-
initialQuote: { source: { actor: { app: "WIDGET" } } },
66+
initialQuote: { source: { actor: { app: "WIDGET", name: "Acme Widget" } } },
6767
},
6868
questionAnswers: [
6969
{
@@ -113,6 +113,7 @@ describe("fromBookingNode", () => {
113113
expect(booking.source).toBe("website");
114114
expect(booking.sourceApp).toBe("WIDGET");
115115
expect(booking.sourceDescription).toBe("Website Booking Flow");
116+
expect(booking.sourceDetails).toBe("Acme Widget");
116117
expect(booking.customerName).toBe("Ada");
117118
expect(booking.isRentalProduct).toBe(true);
118119
expect(booking.timeslotId).toBe("legacy-ts");
@@ -168,6 +169,7 @@ describe("fromBookingNode", () => {
168169
source: "unknown",
169170
sourceApp: "unknown",
170171
sourceDescription: "unknown",
172+
sourceDetails: null,
171173
customerName: "",
172174
customerEmail: null,
173175
productId: "unknown",
@@ -203,6 +205,7 @@ describe("fromBookingNode", () => {
203205
expect(booking.source).toBe("unknown");
204206
expect(booking.sourceDescription).toBe("unknown");
205207
expect(booking.sourceApp).toBe("NOT_A_SOURCE");
208+
expect(booking.sourceDetails).toBe(null);
206209
expect(booking.isRentalProduct).toBe(false);
207210
});
208211

test/bookings/booking-webhook.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { BookingNode } from "../../src/internal/bookings/booking-queries.js
1414
* if it is accidental, this catches it before release.
1515
*/
1616
const REGISTERED_WEBHOOK_QUERY =
17-
"{ displayId id primaryGuest { name email phone optinMarketing optinSms isGdpr postalCode } activitySnapshot { type name id } ticketQuantities { quantity resourceOptionSnapshot { name id } } reservationStatus checkinStatus returnStatus fulfillmentStatusOverride { status } timeSnapshot { id legacyId } purchasedAt purchasedAtUtc startsAt startsAtUtc endsAt endsAtUtc availabilityTimeId bookingPortalUrl operatorNotes value { convenienceFee { amount formatted } deposit { amount formatted } discount { amount formatted } discountedPrice { amount formatted } fees { amount formatted } flatPartnerFee { amount formatted } price { amount formatted } retailPrice { amount formatted } taxes { amount formatted } tips { amount formatted } total { formatted amount } } balance { total { amount formatted } } tips { price { amount formatted } } order { displayId id promoCodes { code } channelSnapshot { id name agent { name } } initialQuote { source { actor { app } } } } questionAnswers { answer questionText questionLocationSnapshot { latitude longitude } } tickets { questionAnswers { answer questionText } } resourcePoolAssignments { quantity resourcePool { name shortName resources { name } } resourceAssignments { resource { id name } } } bookingGuests { id name country dateOfBirth email isGdpr isParticipant optinSms optinMarketing phone postalCode fieldResponses { id text fieldLocation { field { name } } } } primaryGuest { id name country dateOfBirth email isGdpr isParticipant optinSms optinMarketing phone postalCode fieldResponses { id text fieldLocation { field { name } } } } }";
17+
"{ displayId id primaryGuest { name email phone optinMarketing optinSms isGdpr postalCode } activitySnapshot { type name id } ticketQuantities { quantity resourceOptionSnapshot { name id } } reservationStatus checkinStatus returnStatus fulfillmentStatusOverride { status } timeSnapshot { id legacyId } purchasedAt purchasedAtUtc startsAt startsAtUtc endsAt endsAtUtc availabilityTimeId bookingPortalUrl operatorNotes value { convenienceFee { amount formatted } deposit { amount formatted } discount { amount formatted } discountedPrice { amount formatted } fees { amount formatted } flatPartnerFee { amount formatted } price { amount formatted } retailPrice { amount formatted } taxes { amount formatted } tips { amount formatted } total { formatted amount } } balance { total { amount formatted } } tips { price { amount formatted } } order { displayId id promoCodes { code } channelSnapshot { id name agent { name } } initialQuote { source { actor { app name } } } } questionAnswers { answer questionText questionLocationSnapshot { latitude longitude } } tickets { questionAnswers { answer questionText } } resourcePoolAssignments { quantity resourcePool { name shortName resources { name } } resourceAssignments { resource { id name } } } bookingGuests { id name country dateOfBirth email isGdpr isParticipant optinSms optinMarketing phone postalCode fieldResponses { id text fieldLocation { field { name } } } } primaryGuest { id name country dateOfBirth email isGdpr isParticipant optinSms optinMarketing phone postalCode fieldResponses { id text fieldLocation { field { name } } } } }";
1818

1919
function webhookNode(): BookingNode {
2020
return {

0 commit comments

Comments
 (0)