@bash-app/bash-common 30.374.0 → 30.377.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/dist/definitions.d.ts +7 -0
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js.map +1 -1
- package/dist/extendedSchemas.d.ts +57 -1
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js +10 -0
- package/dist/extendedSchemas.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/__tests__/ticketPrintPostageUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/ticketPrintPostageUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/ticketPrintPostageUtils.test.js +70 -0
- package/dist/utils/__tests__/ticketPrintPostageUtils.test.js.map +1 -0
- package/dist/utils/lobFlyerUtils.d.ts +2 -0
- package/dist/utils/lobFlyerUtils.d.ts.map +1 -1
- package/dist/utils/lobFlyerUtils.js +8 -0
- package/dist/utils/lobFlyerUtils.js.map +1 -1
- package/dist/utils/qrCodeUtils.d.ts +17 -0
- package/dist/utils/qrCodeUtils.d.ts.map +1 -1
- package/dist/utils/qrCodeUtils.js +20 -0
- package/dist/utils/qrCodeUtils.js.map +1 -1
- package/dist/utils/service/__tests__/apiServiceBookingApiUtils.test.js +4 -0
- package/dist/utils/service/__tests__/apiServiceBookingApiUtils.test.js.map +1 -1
- package/dist/utils/service/apiServiceBookingApiUtils.js +2 -2
- package/dist/utils/service/apiServiceBookingApiUtils.js.map +1 -1
- package/dist/utils/ticketPrintPostageUtils.d.ts +27 -0
- package/dist/utils/ticketPrintPostageUtils.d.ts.map +1 -0
- package/dist/utils/ticketPrintPostageUtils.js +65 -0
- package/dist/utils/ticketPrintPostageUtils.js.map +1 -0
- package/package.json +1 -1
- package/prisma/schema.prisma +94 -0
- package/src/definitions.ts +8 -0
- package/src/extendedSchemas.ts +64 -0
- package/src/index.ts +1 -0
- package/src/utils/__tests__/ticketPrintPostageUtils.test.ts +95 -0
- package/src/utils/lobFlyerUtils.ts +7 -0
- package/src/utils/qrCodeUtils.ts +26 -0
- package/src/utils/service/__tests__/apiServiceBookingApiUtils.test.ts +6 -0
- package/src/utils/service/apiServiceBookingApiUtils.ts +2 -2
- package/src/utils/ticketPrintPostageUtils.ts +81 -0
package/prisma/schema.prisma
CHANGED
|
@@ -1181,6 +1181,8 @@ model BashEvent {
|
|
|
1181
1181
|
merchOrders BashEventMerchOrder[]
|
|
1182
1182
|
coatCheckEntitlements CoatCheckEntitlement[]
|
|
1183
1183
|
experienceOpportunities ExperienceOpportunity[]
|
|
1184
|
+
ticketPrintOrders TicketPrintOrder[]
|
|
1185
|
+
ticketDeliverySettings TicketDeliverySettings?
|
|
1184
1186
|
|
|
1185
1187
|
/// Latest AI moderator verdict
|
|
1186
1188
|
/// Null = never reviewed by AI. Recomputed each time the bash transitions to Pending
|
|
@@ -3316,6 +3318,9 @@ model TicketTier {
|
|
|
3316
3318
|
tierReleaseReminders TierReleaseReminder[]
|
|
3317
3319
|
entryCompetition Competition? @relation("CompetitionEntryTicketTier")
|
|
3318
3320
|
|
|
3321
|
+
/// Host opt-in: buyers of this tier may choose mailed or venue will-call physical delivery (see TicketPrintOrder).
|
|
3322
|
+
allowsPhysicalDelivery Boolean @default(false)
|
|
3323
|
+
|
|
3319
3324
|
@@unique([bashEventId, title])
|
|
3320
3325
|
@@index([availableAt])
|
|
3321
3326
|
}
|
|
@@ -3464,6 +3469,7 @@ model Ticket {
|
|
|
3464
3469
|
eventReferral EventReferral? // When this ticket was purchased via a referral link
|
|
3465
3470
|
flyerConversions FlyerConversion[]
|
|
3466
3471
|
coatCheckEntitlements CoatCheckEntitlement[]
|
|
3472
|
+
printOrder TicketPrintOrder?
|
|
3467
3473
|
|
|
3468
3474
|
@@index([bashEventId])
|
|
3469
3475
|
@@index([bashEventId, seatId])
|
|
@@ -3556,6 +3562,94 @@ model TicketMetadata {
|
|
|
3556
3562
|
@@index([ticketId])
|
|
3557
3563
|
}
|
|
3558
3564
|
|
|
3565
|
+
enum TicketDeliveryMethod {
|
|
3566
|
+
Digital
|
|
3567
|
+
PhysicalMail
|
|
3568
|
+
VenueWillCall
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
enum TicketPrintOrderStatus {
|
|
3572
|
+
Queued
|
|
3573
|
+
Submitted
|
|
3574
|
+
Printed
|
|
3575
|
+
InTransit
|
|
3576
|
+
Delivered
|
|
3577
|
+
Failed
|
|
3578
|
+
CancelledBeforeSubmit
|
|
3579
|
+
CancelledAfterSubmit
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
/// One row per physical ticket piece mailed or held for venue will-call via Lob.
|
|
3583
|
+
/// Mirrors FlyerCampaign's Lob usage and CoatCheckEntitlement's per-unit purchase/status pattern.
|
|
3584
|
+
model TicketPrintOrder {
|
|
3585
|
+
id String @id @default(cuid())
|
|
3586
|
+
ticketId String @unique
|
|
3587
|
+
bashEventId String
|
|
3588
|
+
|
|
3589
|
+
deliveryMethod TicketDeliveryMethod
|
|
3590
|
+
status TicketPrintOrderStatus @default(Queued)
|
|
3591
|
+
|
|
3592
|
+
/// Ship-to snapshot: buyer address (PhysicalMail, from Stripe shipping_details) or
|
|
3593
|
+
/// venue will-call address (VenueWillCall, from TicketDeliverySettings) at submit time.
|
|
3594
|
+
shipToName String?
|
|
3595
|
+
shipToLine1 String?
|
|
3596
|
+
shipToLine2 String?
|
|
3597
|
+
shipToCity String?
|
|
3598
|
+
shipToState String?
|
|
3599
|
+
shipToZip String?
|
|
3600
|
+
|
|
3601
|
+
lobPostcardSize LobPostcardSize @default(LOB_4X6)
|
|
3602
|
+
lobPostcardId String?
|
|
3603
|
+
vendorStatus String?
|
|
3604
|
+
/// Groups will-call pieces submitted together for host tracking ("Batch #3 — 42 pieces").
|
|
3605
|
+
willCallBatchId String?
|
|
3606
|
+
|
|
3607
|
+
surchargeCents Int @default(0)
|
|
3608
|
+
stripeCheckoutSessionId String?
|
|
3609
|
+
checkoutId String?
|
|
3610
|
+
|
|
3611
|
+
submittedAt DateTime?
|
|
3612
|
+
printedAt DateTime?
|
|
3613
|
+
shippedAt DateTime?
|
|
3614
|
+
deliveredAt DateTime?
|
|
3615
|
+
cancelledAt DateTime?
|
|
3616
|
+
|
|
3617
|
+
lastVendorStatusSyncAt DateTime?
|
|
3618
|
+
lastLobWebhookId String?
|
|
3619
|
+
|
|
3620
|
+
createdAt DateTime @default(now())
|
|
3621
|
+
updatedAt DateTime @updatedAt
|
|
3622
|
+
|
|
3623
|
+
ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
|
|
3624
|
+
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
3625
|
+
|
|
3626
|
+
@@index([bashEventId, deliveryMethod, status])
|
|
3627
|
+
@@index([willCallBatchId])
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
/// Per-event opt-in + venue will-call address for physical ticket delivery; kept separate
|
|
3631
|
+
/// from BashEvent (already very large) — one row per event, created on first opt-in.
|
|
3632
|
+
model TicketDeliverySettings {
|
|
3633
|
+
id String @id @default(cuid())
|
|
3634
|
+
bashEventId String @unique
|
|
3635
|
+
|
|
3636
|
+
physicalMailEnabled Boolean @default(false)
|
|
3637
|
+
willCallEnabled Boolean @default(false)
|
|
3638
|
+
lobPostcardSize LobPostcardSize @default(LOB_4X6)
|
|
3639
|
+
|
|
3640
|
+
willCallAddressName String?
|
|
3641
|
+
willCallAddressLine1 String?
|
|
3642
|
+
willCallAddressLine2 String?
|
|
3643
|
+
willCallAddressCity String?
|
|
3644
|
+
willCallAddressState String?
|
|
3645
|
+
willCallAddressZip String?
|
|
3646
|
+
|
|
3647
|
+
createdAt DateTime @default(now())
|
|
3648
|
+
updatedAt DateTime @updatedAt
|
|
3649
|
+
|
|
3650
|
+
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
3651
|
+
}
|
|
3652
|
+
|
|
3559
3653
|
/// Stripe donation to an event (Checkout session or PaymentIntent); includes optional UTM attribution.
|
|
3560
3654
|
model BashEventDonation {
|
|
3561
3655
|
id String @id @default(cuid())
|
package/src/definitions.ts
CHANGED
|
@@ -1101,6 +1101,14 @@ export interface StripeCreateBashEventTicketsCheckoutSessionArgs extends UtmFiel
|
|
|
1101
1101
|
coatCheckOversized?: boolean;
|
|
1102
1102
|
/** Optional tip cents for coat check (when host enables tips). */
|
|
1103
1103
|
coatCheckTipCents?: number;
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Physical ticket delivery via Lob (mail-to-buyer or venue will-call).
|
|
1107
|
+
* Undefined = digital ticket only (existing flow, unchanged). Server
|
|
1108
|
+
* validates against the event's `TicketDeliverySettings` + each tier's
|
|
1109
|
+
* `allowsPhysicalDelivery` before charging the surcharge.
|
|
1110
|
+
*/
|
|
1111
|
+
ticketDeliveryMethod?: "PhysicalMail" | "VenueWillCall";
|
|
1104
1112
|
}
|
|
1105
1113
|
|
|
1106
1114
|
export interface StripeCreateBashEventDonationCheckoutSessionArgs extends UtmFields {
|
package/src/extendedSchemas.ts
CHANGED
|
@@ -78,6 +78,10 @@ import {
|
|
|
78
78
|
TicketTier,
|
|
79
79
|
TicketTransfer,
|
|
80
80
|
TicketTransferInvite,
|
|
81
|
+
TicketDeliverySettings,
|
|
82
|
+
TicketDeliveryMethod,
|
|
83
|
+
TicketPrintOrder,
|
|
84
|
+
TicketPrintOrderStatus,
|
|
81
85
|
User,
|
|
82
86
|
UserPreferences,
|
|
83
87
|
UserStats,
|
|
@@ -1729,6 +1733,8 @@ export type TicketFlexible = Ticket & {
|
|
|
1729
1733
|
transfers?: TicketTransfer[];
|
|
1730
1734
|
metadata?: TicketMetadata[];
|
|
1731
1735
|
transferInvite?: TicketTransferInviteTeaser | null;
|
|
1736
|
+
/** Physical delivery (mail / venue will-call) status, when the ticket opted in at checkout. */
|
|
1737
|
+
printOrder?: TicketPrintOrder | null;
|
|
1732
1738
|
};
|
|
1733
1739
|
|
|
1734
1740
|
/**
|
|
@@ -2114,6 +2120,64 @@ export interface FlyerAnalyticsSummary {
|
|
|
2114
2120
|
|
|
2115
2121
|
export type PostalCarrierRouteExt = PostalCarrierRoute;
|
|
2116
2122
|
|
|
2123
|
+
// --- Physical ticket delivery (mail / venue will-call via Lob) ---
|
|
2124
|
+
|
|
2125
|
+
/** Delivery method a buyer picks at checkout when the tier allows physical delivery. */
|
|
2126
|
+
export interface TicketDeliverySelection {
|
|
2127
|
+
deliveryMethod: TicketDeliveryMethod;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
export const TICKET_PRINT_ORDER_WITH_TICKET_INCLUDE = {
|
|
2131
|
+
ticket: {
|
|
2132
|
+
select: {
|
|
2133
|
+
id: true,
|
|
2134
|
+
fullName: true,
|
|
2135
|
+
email: true,
|
|
2136
|
+
ticketTier: { select: { id: true, title: true } },
|
|
2137
|
+
},
|
|
2138
|
+
},
|
|
2139
|
+
} satisfies Prisma.TicketPrintOrderInclude;
|
|
2140
|
+
|
|
2141
|
+
export type TicketPrintOrderExt = Prisma.TicketPrintOrderGetPayload<{
|
|
2142
|
+
include: typeof TICKET_PRINT_ORDER_WITH_TICKET_INCLUDE;
|
|
2143
|
+
}>;
|
|
2144
|
+
|
|
2145
|
+
/** Host-facing form for enabling/configuring physical delivery on an event. */
|
|
2146
|
+
export interface TicketDeliverySettingsFormData {
|
|
2147
|
+
physicalMailEnabled: boolean;
|
|
2148
|
+
willCallEnabled: boolean;
|
|
2149
|
+
lobPostcardSize: TicketDeliverySettings["lobPostcardSize"];
|
|
2150
|
+
willCallAddressName?: string;
|
|
2151
|
+
willCallAddressLine1?: string;
|
|
2152
|
+
willCallAddressLine2?: string;
|
|
2153
|
+
willCallAddressCity?: string;
|
|
2154
|
+
willCallAddressState?: string;
|
|
2155
|
+
willCallAddressZip?: string;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
/** Response from the host "send queued will-call tickets to venue" action. */
|
|
2159
|
+
export interface WillCallBatchSubmitResult {
|
|
2160
|
+
willCallBatchId: string;
|
|
2161
|
+
submittedCount: number;
|
|
2162
|
+
failedCount: number;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
/** Summary counts for the host Tickets tab "Print & Mail" panel. */
|
|
2166
|
+
export interface TicketPrintOrderSummary {
|
|
2167
|
+
deliveryMethod: TicketDeliveryMethod;
|
|
2168
|
+
status: TicketPrintOrderStatus;
|
|
2169
|
+
count: number;
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
/** Public-safe checkout-time read of an event's physical delivery settings. */
|
|
2173
|
+
export interface TicketDeliveryAvailability {
|
|
2174
|
+
physicalMailEnabled: boolean;
|
|
2175
|
+
willCallEnabled: boolean;
|
|
2176
|
+
lobPostcardSize: TicketDeliverySettings["lobPostcardSize"];
|
|
2177
|
+
/** Buyer-facing surcharge (integer cents) for one physical ticket piece. */
|
|
2178
|
+
surchargeCentsPerTicket: number;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2117
2181
|
// === Talent Agency selects/types ============================================
|
|
2118
2182
|
|
|
2119
2183
|
/// Public-safe roster entry for an agency's artist (agency detail page + cards).
|
package/src/index.ts
CHANGED
|
@@ -416,6 +416,7 @@ export * from "./utils/ticketCheckInDisplay.js";
|
|
|
416
416
|
export * from "./utils/coatCheckUtils.js";
|
|
417
417
|
export * from "./utils/ticketTierSchedule.js";
|
|
418
418
|
export * from "./utils/lobFlyerUtils.js";
|
|
419
|
+
export * from "./utils/ticketPrintPostageUtils.js";
|
|
419
420
|
export * from "./utils/generalDateTimeUtils.js";
|
|
420
421
|
export * from "./utils/locationVisibility.js";
|
|
421
422
|
export * from "./utils/luxonUtils.js";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, expect, it } from "@jest/globals";
|
|
2
|
+
import { TicketDeliveryMethod, TicketPrintOrderStatus } from "@prisma/client";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ticketDeliveryStatusLabel,
|
|
6
|
+
ticketPrintSurchargeCentsForQuantity,
|
|
7
|
+
ticketPrintSurchargeCentsPerPiece,
|
|
8
|
+
} from "../ticketPrintPostageUtils.js";
|
|
9
|
+
|
|
10
|
+
describe("ticketPrintPostageUtils", () => {
|
|
11
|
+
describe("ticketPrintSurchargeCentsPerPiece", () => {
|
|
12
|
+
it("matches the per-piece quote for quantity 1", () => {
|
|
13
|
+
expect(ticketPrintSurchargeCentsPerPiece("LOB_4X6")).toBe(
|
|
14
|
+
ticketPrintSurchargeCentsForQuantity("LOB_4X6", 1)
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("ticketPrintSurchargeCentsForQuantity", () => {
|
|
20
|
+
it("is zero for quantity < 1", () => {
|
|
21
|
+
expect(ticketPrintSurchargeCentsForQuantity("LOB_4X6", 0)).toBe(0);
|
|
22
|
+
expect(ticketPrintSurchargeCentsForQuantity("LOB_4X6", -1)).toBe(0);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("scales with quantity", () => {
|
|
26
|
+
const one = ticketPrintSurchargeCentsForQuantity("LOB_6X9", 1);
|
|
27
|
+
const five = ticketPrintSurchargeCentsForQuantity("LOB_6X9", 5);
|
|
28
|
+
expect(five).toBeGreaterThan(one);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("ticketDeliveryStatusLabel", () => {
|
|
33
|
+
it("returns null for Digital (no print order)", () => {
|
|
34
|
+
expect(ticketDeliveryStatusLabel(null)).toBeNull();
|
|
35
|
+
expect(ticketDeliveryStatusLabel(undefined)).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("returns a cancelled label regardless of delivery method (before submit)", () => {
|
|
39
|
+
expect(
|
|
40
|
+
ticketDeliveryStatusLabel({
|
|
41
|
+
deliveryMethod: TicketDeliveryMethod.PhysicalMail,
|
|
42
|
+
status: TicketPrintOrderStatus.CancelledBeforeSubmit,
|
|
43
|
+
})
|
|
44
|
+
).toBe("Physical ticket cancelled");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("returns a cancelled label (after submit)", () => {
|
|
48
|
+
expect(
|
|
49
|
+
ticketDeliveryStatusLabel({
|
|
50
|
+
deliveryMethod: TicketDeliveryMethod.VenueWillCall,
|
|
51
|
+
status: TicketPrintOrderStatus.CancelledAfterSubmit,
|
|
52
|
+
})
|
|
53
|
+
).toBe("Physical ticket cancelled");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("returns a failure label", () => {
|
|
57
|
+
expect(
|
|
58
|
+
ticketDeliveryStatusLabel({
|
|
59
|
+
deliveryMethod: TicketDeliveryMethod.PhysicalMail,
|
|
60
|
+
status: TicketPrintOrderStatus.Failed,
|
|
61
|
+
})
|
|
62
|
+
).toBe("Physical ticket delivery failed — contact the host");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it.each([
|
|
66
|
+
[TicketPrintOrderStatus.Queued, "Pick up at venue: will-call (printing soon)"],
|
|
67
|
+
[TicketPrintOrderStatus.Submitted, "Pick up at venue: will-call"],
|
|
68
|
+
[TicketPrintOrderStatus.Printed, "Pick up at venue: will-call"],
|
|
69
|
+
[TicketPrintOrderStatus.InTransit, "Pick up at venue: will-call"],
|
|
70
|
+
[TicketPrintOrderStatus.Delivered, "Pick up at venue: will-call (ready)"],
|
|
71
|
+
])("VenueWillCall %s -> %s", (status, expected) => {
|
|
72
|
+
expect(
|
|
73
|
+
ticketDeliveryStatusLabel({
|
|
74
|
+
deliveryMethod: TicketDeliveryMethod.VenueWillCall,
|
|
75
|
+
status,
|
|
76
|
+
})
|
|
77
|
+
).toBe(expected);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it.each([
|
|
81
|
+
[TicketPrintOrderStatus.Queued, "Mailed ticket: printing"],
|
|
82
|
+
[TicketPrintOrderStatus.Submitted, "Mailed ticket: printing"],
|
|
83
|
+
[TicketPrintOrderStatus.Printed, "Mailed ticket: printed"],
|
|
84
|
+
[TicketPrintOrderStatus.InTransit, "Mailed ticket: in transit"],
|
|
85
|
+
[TicketPrintOrderStatus.Delivered, "Mailed ticket: delivered"],
|
|
86
|
+
])("PhysicalMail %s -> %s", (status, expected) => {
|
|
87
|
+
expect(
|
|
88
|
+
ticketDeliveryStatusLabel({
|
|
89
|
+
deliveryMethod: TicketDeliveryMethod.PhysicalMail,
|
|
90
|
+
status,
|
|
91
|
+
})
|
|
92
|
+
).toBe(expected);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
|
|
14
14
|
export type LobPostcardSizeKey = "LOB_4X6" | "LOB_6X9" | "LOB_6X11";
|
|
15
15
|
|
|
16
|
+
/** Narrows the Prisma `LobPostcardSize` enum value to the string-literal key used by these utils. */
|
|
17
|
+
export function lobPostcardSizeEnumToKey(size: LobPostcardSizeKey | string): LobPostcardSizeKey {
|
|
18
|
+
if (size === "LOB_6X9") return "LOB_6X9";
|
|
19
|
+
if (size === "LOB_6X11") return "LOB_6X11";
|
|
20
|
+
return "LOB_4X6";
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
/** Trim width × height in inches (portrait), matching Lob postcard products. */
|
|
17
24
|
export const LOB_POSTCARD_TRIM_IN: Record<
|
|
18
25
|
LobPostcardSizeKey,
|
package/src/utils/qrCodeUtils.ts
CHANGED
|
@@ -24,3 +24,29 @@ export async function createQrCodeImage(qrData: string): Promise<string> {
|
|
|
24
24
|
errorCorrectionLevel: 'high',
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Door-scan admission QR payload for a ticket — the same shape the buyer's
|
|
30
|
+
* "show at door" QR encodes (bash-app `MyTicketInlineQr.tsx`, built inline as
|
|
31
|
+
* `checkInData` for `encodeTicketQrPayload`) and what the API's door-verify
|
|
32
|
+
* parser reads (`parseTicketQrScanPayload` in `ticketApi.ts`, which requires
|
|
33
|
+
* string `eventId`/`ticketId` and reads an optional `qrToken`).
|
|
34
|
+
*
|
|
35
|
+
* Server-side callers (e.g. printed ticket artwork) MUST use this so a
|
|
36
|
+
* physical ticket's QR scans identically to the in-app one — no new
|
|
37
|
+
* verification path, no schema changes to `qrToken` handling.
|
|
38
|
+
*/
|
|
39
|
+
export function encodeTicketDoorQrPayload(args: {
|
|
40
|
+
eventId: string;
|
|
41
|
+
ticketId: string;
|
|
42
|
+
ownerUserId?: string | null;
|
|
43
|
+
qrToken?: string | null;
|
|
44
|
+
}): string {
|
|
45
|
+
return JSON.stringify({
|
|
46
|
+
eventId: args.eventId,
|
|
47
|
+
userId: args.ownerUserId ?? "",
|
|
48
|
+
ticketId: args.ticketId,
|
|
49
|
+
qrToken: args.qrToken ?? undefined,
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -148,6 +148,12 @@ describe("servicePriceToBookToApiPackages", () => {
|
|
|
148
148
|
expect(servicePriceToBookToApiPackages(priceToBook)).toEqual([]);
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
it("returns empty array when priceToBook.packages is undefined", () => {
|
|
152
|
+
const priceToBook = {} as unknown as FrontendServiceGetPriceToBookResult;
|
|
153
|
+
|
|
154
|
+
expect(servicePriceToBookToApiPackages(priceToBook)).toEqual([]);
|
|
155
|
+
});
|
|
156
|
+
|
|
151
157
|
it("maps priceToBook packages to ApiServicePackageParams", () => {
|
|
152
158
|
const priceToBook = {
|
|
153
159
|
packages: [
|
|
@@ -270,7 +270,7 @@ export function servicePriceToBookToApiDays(
|
|
|
270
270
|
export function servicePriceToBookToApiPackages(
|
|
271
271
|
priceToBook: FrontendServiceGetPriceToBookResult
|
|
272
272
|
): ApiServicePackageParams[] {
|
|
273
|
-
return priceToBook.packages.map(
|
|
273
|
+
return (priceToBook.packages ?? []).map(
|
|
274
274
|
(pkg): ApiServicePackageParams => ({
|
|
275
275
|
packageId: pkg.packageId,
|
|
276
276
|
chosenQuantity: pkg.chosenQuantity,
|
|
@@ -304,7 +304,7 @@ export function servicePriceToBookToApiBookingBase({
|
|
|
304
304
|
costPQCents: addOn.costPQCents,
|
|
305
305
|
})
|
|
306
306
|
);
|
|
307
|
-
const packages = priceToBook.packages.map(
|
|
307
|
+
const packages = (priceToBook.packages ?? []).map(
|
|
308
308
|
(pkg): Partial<ServiceBookingPackageExt> => ({
|
|
309
309
|
packageId: pkg.packageId,
|
|
310
310
|
package: pkg.package,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buyer-facing surcharge for physical ticket delivery (mail-to-buyer or venue
|
|
3
|
+
* will-call), both fulfilled as individually-addressed Lob postcards — one
|
|
4
|
+
* ticket = one piece, same product used by Flyer Blast's addressed mail path.
|
|
5
|
+
*
|
|
6
|
+
* Reuses the Lob per-piece cost table from `lobFlyerUtils.ts` rather than
|
|
7
|
+
* duplicating pricing — same "verify before production" caveat applies here.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { TicketDeliveryMethod, TicketPrintOrderStatus } from "@prisma/client";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
quoteLobAddressedPostageCents,
|
|
14
|
+
type LobPostcardSizeKey,
|
|
15
|
+
} from "./lobFlyerUtils.js";
|
|
16
|
+
|
|
17
|
+
/** Surcharge for a single printed/mailed ticket piece (integer cents). */
|
|
18
|
+
export function ticketPrintSurchargeCentsPerPiece(size: LobPostcardSizeKey): number {
|
|
19
|
+
return quoteLobAddressedPostageCents(size, 1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Total buyer-facing surcharge (integer cents) for `quantity` physical ticket
|
|
24
|
+
* pieces of the given size — used as the Stripe checkout line item amount.
|
|
25
|
+
*/
|
|
26
|
+
export function ticketPrintSurchargeCentsForQuantity(
|
|
27
|
+
size: LobPostcardSizeKey,
|
|
28
|
+
quantity: number
|
|
29
|
+
): number {
|
|
30
|
+
if (quantity < 1) return 0;
|
|
31
|
+
return quoteLobAddressedPostageCents(size, quantity);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Buyer-facing badge copy for a ticket's physical delivery status — shared by
|
|
36
|
+
* My Tickets and the ticket details view so both surfaces stay in sync.
|
|
37
|
+
* `null`/`undefined` (Digital) means no badge should render.
|
|
38
|
+
*/
|
|
39
|
+
export function ticketDeliveryStatusLabel(
|
|
40
|
+
printOrder:
|
|
41
|
+
| { deliveryMethod: TicketDeliveryMethod; status: TicketPrintOrderStatus }
|
|
42
|
+
| null
|
|
43
|
+
| undefined
|
|
44
|
+
): string | null {
|
|
45
|
+
if (!printOrder) return null;
|
|
46
|
+
const { deliveryMethod, status } = printOrder;
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
status === TicketPrintOrderStatus.CancelledBeforeSubmit ||
|
|
50
|
+
status === TicketPrintOrderStatus.CancelledAfterSubmit
|
|
51
|
+
) {
|
|
52
|
+
return "Physical ticket cancelled";
|
|
53
|
+
}
|
|
54
|
+
if (status === TicketPrintOrderStatus.Failed) {
|
|
55
|
+
return "Physical ticket delivery failed — contact the host";
|
|
56
|
+
}
|
|
57
|
+
if (deliveryMethod === TicketDeliveryMethod.VenueWillCall) {
|
|
58
|
+
switch (status) {
|
|
59
|
+
case TicketPrintOrderStatus.Queued:
|
|
60
|
+
return "Pick up at venue: will-call (printing soon)";
|
|
61
|
+
case TicketPrintOrderStatus.Delivered:
|
|
62
|
+
return "Pick up at venue: will-call (ready)";
|
|
63
|
+
default:
|
|
64
|
+
return "Pick up at venue: will-call";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// PhysicalMail
|
|
68
|
+
switch (status) {
|
|
69
|
+
case TicketPrintOrderStatus.Queued:
|
|
70
|
+
case TicketPrintOrderStatus.Submitted:
|
|
71
|
+
return "Mailed ticket: printing";
|
|
72
|
+
case TicketPrintOrderStatus.Printed:
|
|
73
|
+
return "Mailed ticket: printed";
|
|
74
|
+
case TicketPrintOrderStatus.InTransit:
|
|
75
|
+
return "Mailed ticket: in transit";
|
|
76
|
+
case TicketPrintOrderStatus.Delivered:
|
|
77
|
+
return "Mailed ticket: delivered";
|
|
78
|
+
default:
|
|
79
|
+
return "Mailed ticket";
|
|
80
|
+
}
|
|
81
|
+
}
|