@frontdesk-africa/store-js 0.3.0 → 0.4.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/README.md +31 -1
- package/dist/index.d.mts +59 -1
- package/dist/index.d.ts +59 -1
- package/dist/index.js +14 -0
- package/dist/index.mjs +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ MCP: `POST /v1/store/mcp`.
|
|
|
16
16
|
|
|
17
17
|
| Key | Where it goes | What it does |
|
|
18
18
|
|---|---|---|
|
|
19
|
-
| `fd_pk_…` | your **page**, safe to ship |
|
|
19
|
+
| `fd_pk_…` | your **page**, safe to ship | reads, plus free event registration, and only from origins the merchant registered |
|
|
20
20
|
| `fd_sk_…` | your **server**, never a page | opens checkouts |
|
|
21
21
|
|
|
22
22
|
A publishable key is designed to be public. It is locked to exact origins, and an empty origin list
|
|
@@ -70,6 +70,28 @@ const chosen = item.variants.find(
|
|
|
70
70
|
Dim a value when every variant still holding it is `soldOut` (or has no price in the buyer's
|
|
71
71
|
currency), and swap your gallery to a value's `media` when it is selected.
|
|
72
72
|
|
|
73
|
+
## Free event registration (browser)
|
|
74
|
+
|
|
75
|
+
A free RSVP page needs no server. Read the event, offer the tiers whose `kind` is `free`, ask the
|
|
76
|
+
questions in each tier's `formFields`, and post the registration with the publishable key.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const event = await store.event('open-house')
|
|
80
|
+
const tier = event.ticketTypes.find((t) => t.kind === 'free')!
|
|
81
|
+
|
|
82
|
+
const done = await store.registerForEvent(event.slug, {
|
|
83
|
+
tickets: [{ ticketTypeRef: tier.ref, quantity: 2, attendees: [{}, { name: 'Tunde Obi', email: 'tunde@example.com' }] }],
|
|
84
|
+
contact: { name: 'Ada Obi', email: 'ada@example.com', phone: '+2348120700080' },
|
|
85
|
+
responses: { piggyvest_user: 'Yes', first_time: 'No' }, // keyed by formFields[].key
|
|
86
|
+
})
|
|
87
|
+
// done.status → 'confirmed' | 'pending_approval' | 'waitlisted'; done.reference → 'EV-7F3A2C'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
FrontDesk emails the guest their confirmation, calendar invite and ticket. A paid tier is refused
|
|
91
|
+
with `TIER_REQUIRES_PAYMENT`: open a checkout for it from your server. Registering the same email
|
|
92
|
+
twice returns the same answer and re-sends the confirmation instead of issuing a second seat, so a
|
|
93
|
+
retry is safe. To read registrations back, subscribe to the `attendee.registered` webhook.
|
|
94
|
+
|
|
73
95
|
## Checkout (server)
|
|
74
96
|
|
|
75
97
|
You never handle card details and you never price a cart. The buyer pays on a FrontDesk-hosted page
|
|
@@ -102,6 +124,10 @@ if (done.status === 'completed') fulfil(done.orderRef)
|
|
|
102
124
|
**Do not fulfil on the redirect itself.** It is a browser navigation: it can be lost, replayed or
|
|
103
125
|
forged. `getCheckout` or the `checkout.completed` webhook are the only proof.
|
|
104
126
|
|
|
127
|
+
Only a paid checkout reaches `returnUrl`. A buyer who backs out of a payment comes back to the hosted
|
|
128
|
+
page with the same cart and a link to your site, a retry there pays the same order rather than opening
|
|
129
|
+
another, and reopening `hostedUrl` resumes it, including a bank transfer already started.
|
|
130
|
+
|
|
105
131
|
Event tickets work the same way, with the event telling you what to collect first:
|
|
106
132
|
|
|
107
133
|
```ts
|
|
@@ -208,6 +234,10 @@ Paste this, with your key:
|
|
|
208
234
|
> Pages: home rendering `GET /v1/store/storefront`, a product list from `GET /v1/store/products`, and
|
|
209
235
|
> a product page from `GET /v1/store/products/{slug}`.
|
|
210
236
|
>
|
|
237
|
+
> For a free event RSVP page, call `POST /v1/store/events/{slug}/register` straight from the
|
|
238
|
+
> browser with the publishable key, sending `tickets`, `contact` and `responses` keyed by each
|
|
239
|
+
> tier's `formFields[].key`. FrontDesk sends the confirmation email itself.
|
|
240
|
+
>
|
|
211
241
|
> For checkout, call `POST /v1/store/checkouts` **from a server route, never the browser**, using the
|
|
212
242
|
> secret key, with an `Idempotency-Key` header derived from the cart id. Redirect the buyer to the
|
|
213
243
|
> `hostedUrl` you get back. Do not build a payment form and do not ask for card details.
|
package/dist/index.d.mts
CHANGED
|
@@ -984,7 +984,8 @@ interface StoreCheckoutSessionInput {
|
|
|
984
984
|
contact?: Partial<StoreCheckoutContact> | null;
|
|
985
985
|
deliveryZoneRef?: string | null;
|
|
986
986
|
shipping?: StoreShippingAddress | null;
|
|
987
|
-
/** Must be
|
|
987
|
+
/** The currency the buyer pays in. Must be one the store accepts, with every item priced in it, else
|
|
988
|
+
* VALIDATION_ERROR. Omit for the store primary. Fixed at open; never re-priced in another currency. */
|
|
988
989
|
currency?: string | null;
|
|
989
990
|
/** Where we send the buyer after payment. Must be https (or localhost while testing). */
|
|
990
991
|
returnUrl: string;
|
|
@@ -1083,6 +1084,7 @@ interface StoreCheckoutSessionView {
|
|
|
1083
1084
|
/** Send the buyer here. Valid until `expiresAt`. */
|
|
1084
1085
|
hostedUrl: string;
|
|
1085
1086
|
returnUrl: string;
|
|
1087
|
+
/** The currency the checkout is priced and charged in. Always set on a product checkout; immutable. */
|
|
1086
1088
|
currency: string | null;
|
|
1087
1089
|
/**
|
|
1088
1090
|
* The order this became, once the buyer paid. Null while open.
|
|
@@ -2447,6 +2449,52 @@ interface PublicForm {
|
|
|
2447
2449
|
uploadToken?: string;
|
|
2448
2450
|
}
|
|
2449
2451
|
|
|
2452
|
+
/**
|
|
2453
|
+
* FrontDesk public storefront contract (Storefront API §3.2). These shapes are what a
|
|
2454
|
+
* merchant-built frontend receives, so they are additive-only: never remove a field, never retype
|
|
2455
|
+
* one, never narrow an enum. Guarded by `pnpm --filter pro-api store-contract:check`.
|
|
2456
|
+
*/
|
|
2457
|
+
|
|
2458
|
+
type StoreEventRegistrationStatus = 'confirmed' | 'pending_approval' | 'waitlisted';
|
|
2459
|
+
/** Like StoreCheckoutContact, except a phone may stand in for the email. One of the two is required. */
|
|
2460
|
+
interface StoreEventRegistrationContact {
|
|
2461
|
+
name: string;
|
|
2462
|
+
/** Receives the confirmation, the calendar invite and the ticket. */
|
|
2463
|
+
email?: string | null;
|
|
2464
|
+
/** Receives an SMS confirmation when the event sends them, or when there is no email. */
|
|
2465
|
+
phone?: string | null;
|
|
2466
|
+
}
|
|
2467
|
+
/**
|
|
2468
|
+
* A subset of StoreEventCheckoutInput: the same `tickets[]` (positional `attendees`, length
|
|
2469
|
+
* quantity x groupSize, a blank entry keeps that seat under the registrant), no returnUrl, no
|
|
2470
|
+
* currency, no discountCode. Every tier must be FREE.
|
|
2471
|
+
*/
|
|
2472
|
+
interface StoreEventRegistrationInput {
|
|
2473
|
+
tickets: StoreTicketItem[];
|
|
2474
|
+
contact: StoreEventRegistrationContact;
|
|
2475
|
+
/** YYYY-MM-DD. Required when a chosen tier sets minAge and the event dobMode is order. */
|
|
2476
|
+
dateOfBirth?: string | null;
|
|
2477
|
+
/** Answers to the chosen tiers' formFields, keyed by each field's `key`. */
|
|
2478
|
+
responses?: Record<string, unknown> | null;
|
|
2479
|
+
/** Codes that reveal hidden or invite-only tiers. */
|
|
2480
|
+
unlockCodes?: string[] | null;
|
|
2481
|
+
/** Affiliate code from a share link. Ignored if unknown. */
|
|
2482
|
+
promoterCode?: string | null;
|
|
2483
|
+
}
|
|
2484
|
+
interface StoreEventRegistrationView {
|
|
2485
|
+
eventSlug: string;
|
|
2486
|
+
eventRef: string;
|
|
2487
|
+
ticketTypeRef: string;
|
|
2488
|
+
status: StoreEventRegistrationStatus;
|
|
2489
|
+
/** The guest's short reference (EV-XXXXXX), the same one printed on their email. Never a ticket token. */
|
|
2490
|
+
reference: string;
|
|
2491
|
+
/** Seats issued by this registration (quantity x groupSize across the lines). */
|
|
2492
|
+
attendeeCount: number;
|
|
2493
|
+
/** True from a test key: everything was validated, nothing was written, nothing was sent. */
|
|
2494
|
+
test: boolean;
|
|
2495
|
+
message: string;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2450
2498
|
/**
|
|
2451
2499
|
* Typed client for the FrontDesk Storefront API.
|
|
2452
2500
|
*
|
|
@@ -2514,6 +2562,16 @@ declare function createStoreClient(opts: StoreClientOptions): {
|
|
|
2514
2562
|
availability: (variantRefs: string[]) => Promise<PublicStoreAvailability>;
|
|
2515
2563
|
/** The payment rails this workspace can charge, for rendering your own picker (headless only). */
|
|
2516
2564
|
paymentMethods: (currency?: string) => Promise<string[]>;
|
|
2565
|
+
/**
|
|
2566
|
+
* Register a guest for a FREE event tier. The one write a publishable key may make, so call it
|
|
2567
|
+
* straight from the page. `tickets` has the checkout shape (positional `attendees`, length
|
|
2568
|
+
* quantity x groupSize); `responses` answers the tier's `formFields` by `key`. A paid tier is
|
|
2569
|
+
* refused with TIER_REQUIRES_PAYMENT: open a checkout for it from your server. Duplicates are
|
|
2570
|
+
* quiet (an email that already holds a seat gets the same answer and its confirmation again,
|
|
2571
|
+
* never a second seat), so retrying is safe and no idempotency key is needed. FrontDesk sends
|
|
2572
|
+
* the confirmation, the .ics invite and the ticket itself.
|
|
2573
|
+
*/
|
|
2574
|
+
registerForEvent: (slug: string, input: StoreEventRegistrationInput) => Promise<StoreEventRegistrationView>;
|
|
2517
2575
|
/**
|
|
2518
2576
|
* Open a hosted checkout. SERVER ONLY — needs a secret key.
|
|
2519
2577
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -984,7 +984,8 @@ interface StoreCheckoutSessionInput {
|
|
|
984
984
|
contact?: Partial<StoreCheckoutContact> | null;
|
|
985
985
|
deliveryZoneRef?: string | null;
|
|
986
986
|
shipping?: StoreShippingAddress | null;
|
|
987
|
-
/** Must be
|
|
987
|
+
/** The currency the buyer pays in. Must be one the store accepts, with every item priced in it, else
|
|
988
|
+
* VALIDATION_ERROR. Omit for the store primary. Fixed at open; never re-priced in another currency. */
|
|
988
989
|
currency?: string | null;
|
|
989
990
|
/** Where we send the buyer after payment. Must be https (or localhost while testing). */
|
|
990
991
|
returnUrl: string;
|
|
@@ -1083,6 +1084,7 @@ interface StoreCheckoutSessionView {
|
|
|
1083
1084
|
/** Send the buyer here. Valid until `expiresAt`. */
|
|
1084
1085
|
hostedUrl: string;
|
|
1085
1086
|
returnUrl: string;
|
|
1087
|
+
/** The currency the checkout is priced and charged in. Always set on a product checkout; immutable. */
|
|
1086
1088
|
currency: string | null;
|
|
1087
1089
|
/**
|
|
1088
1090
|
* The order this became, once the buyer paid. Null while open.
|
|
@@ -2447,6 +2449,52 @@ interface PublicForm {
|
|
|
2447
2449
|
uploadToken?: string;
|
|
2448
2450
|
}
|
|
2449
2451
|
|
|
2452
|
+
/**
|
|
2453
|
+
* FrontDesk public storefront contract (Storefront API §3.2). These shapes are what a
|
|
2454
|
+
* merchant-built frontend receives, so they are additive-only: never remove a field, never retype
|
|
2455
|
+
* one, never narrow an enum. Guarded by `pnpm --filter pro-api store-contract:check`.
|
|
2456
|
+
*/
|
|
2457
|
+
|
|
2458
|
+
type StoreEventRegistrationStatus = 'confirmed' | 'pending_approval' | 'waitlisted';
|
|
2459
|
+
/** Like StoreCheckoutContact, except a phone may stand in for the email. One of the two is required. */
|
|
2460
|
+
interface StoreEventRegistrationContact {
|
|
2461
|
+
name: string;
|
|
2462
|
+
/** Receives the confirmation, the calendar invite and the ticket. */
|
|
2463
|
+
email?: string | null;
|
|
2464
|
+
/** Receives an SMS confirmation when the event sends them, or when there is no email. */
|
|
2465
|
+
phone?: string | null;
|
|
2466
|
+
}
|
|
2467
|
+
/**
|
|
2468
|
+
* A subset of StoreEventCheckoutInput: the same `tickets[]` (positional `attendees`, length
|
|
2469
|
+
* quantity x groupSize, a blank entry keeps that seat under the registrant), no returnUrl, no
|
|
2470
|
+
* currency, no discountCode. Every tier must be FREE.
|
|
2471
|
+
*/
|
|
2472
|
+
interface StoreEventRegistrationInput {
|
|
2473
|
+
tickets: StoreTicketItem[];
|
|
2474
|
+
contact: StoreEventRegistrationContact;
|
|
2475
|
+
/** YYYY-MM-DD. Required when a chosen tier sets minAge and the event dobMode is order. */
|
|
2476
|
+
dateOfBirth?: string | null;
|
|
2477
|
+
/** Answers to the chosen tiers' formFields, keyed by each field's `key`. */
|
|
2478
|
+
responses?: Record<string, unknown> | null;
|
|
2479
|
+
/** Codes that reveal hidden or invite-only tiers. */
|
|
2480
|
+
unlockCodes?: string[] | null;
|
|
2481
|
+
/** Affiliate code from a share link. Ignored if unknown. */
|
|
2482
|
+
promoterCode?: string | null;
|
|
2483
|
+
}
|
|
2484
|
+
interface StoreEventRegistrationView {
|
|
2485
|
+
eventSlug: string;
|
|
2486
|
+
eventRef: string;
|
|
2487
|
+
ticketTypeRef: string;
|
|
2488
|
+
status: StoreEventRegistrationStatus;
|
|
2489
|
+
/** The guest's short reference (EV-XXXXXX), the same one printed on their email. Never a ticket token. */
|
|
2490
|
+
reference: string;
|
|
2491
|
+
/** Seats issued by this registration (quantity x groupSize across the lines). */
|
|
2492
|
+
attendeeCount: number;
|
|
2493
|
+
/** True from a test key: everything was validated, nothing was written, nothing was sent. */
|
|
2494
|
+
test: boolean;
|
|
2495
|
+
message: string;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2450
2498
|
/**
|
|
2451
2499
|
* Typed client for the FrontDesk Storefront API.
|
|
2452
2500
|
*
|
|
@@ -2514,6 +2562,16 @@ declare function createStoreClient(opts: StoreClientOptions): {
|
|
|
2514
2562
|
availability: (variantRefs: string[]) => Promise<PublicStoreAvailability>;
|
|
2515
2563
|
/** The payment rails this workspace can charge, for rendering your own picker (headless only). */
|
|
2516
2564
|
paymentMethods: (currency?: string) => Promise<string[]>;
|
|
2565
|
+
/**
|
|
2566
|
+
* Register a guest for a FREE event tier. The one write a publishable key may make, so call it
|
|
2567
|
+
* straight from the page. `tickets` has the checkout shape (positional `attendees`, length
|
|
2568
|
+
* quantity x groupSize); `responses` answers the tier's `formFields` by `key`. A paid tier is
|
|
2569
|
+
* refused with TIER_REQUIRES_PAYMENT: open a checkout for it from your server. Duplicates are
|
|
2570
|
+
* quiet (an email that already holds a seat gets the same answer and its confirmation again,
|
|
2571
|
+
* never a second seat), so retrying is safe and no idempotency key is needed. FrontDesk sends
|
|
2572
|
+
* the confirmation, the .ics invite and the ticket itself.
|
|
2573
|
+
*/
|
|
2574
|
+
registerForEvent: (slug: string, input: StoreEventRegistrationInput) => Promise<StoreEventRegistrationView>;
|
|
2517
2575
|
/**
|
|
2518
2576
|
* Open a hosted checkout. SERVER ONLY — needs a secret key.
|
|
2519
2577
|
*
|
package/dist/index.js
CHANGED
|
@@ -116,6 +116,20 @@ function createStoreClient(opts) {
|
|
|
116
116
|
availability: /* @__PURE__ */ __name((variantRefs) => request(`/store/availability?variantRefs=${enc(variantRefs.join(","))}`), "availability"),
|
|
117
117
|
/** The payment rails this workspace can charge, for rendering your own picker (headless only). */
|
|
118
118
|
paymentMethods: /* @__PURE__ */ __name((currency) => request(`/store/payment-methods${currency ? `?currency=${enc(currency)}` : ""}`), "paymentMethods"),
|
|
119
|
+
// ------------------------------------------ register (fd_pk_ in the browser, or fd_sk_)
|
|
120
|
+
/**
|
|
121
|
+
* Register a guest for a FREE event tier. The one write a publishable key may make, so call it
|
|
122
|
+
* straight from the page. `tickets` has the checkout shape (positional `attendees`, length
|
|
123
|
+
* quantity x groupSize); `responses` answers the tier's `formFields` by `key`. A paid tier is
|
|
124
|
+
* refused with TIER_REQUIRES_PAYMENT: open a checkout for it from your server. Duplicates are
|
|
125
|
+
* quiet (an email that already holds a seat gets the same answer and its confirmation again,
|
|
126
|
+
* never a second seat), so retrying is safe and no idempotency key is needed. FrontDesk sends
|
|
127
|
+
* the confirmation, the .ics invite and the ticket itself.
|
|
128
|
+
*/
|
|
129
|
+
registerForEvent: /* @__PURE__ */ __name((slug, input) => request(`/store/events/${enc(slug)}/register`, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
body: JSON.stringify(input)
|
|
132
|
+
}), "registerForEvent"),
|
|
119
133
|
// --------------------------------------------------- checkout (fd_sk_, server)
|
|
120
134
|
/**
|
|
121
135
|
* Open a hosted checkout. SERVER ONLY — needs a secret key.
|
package/dist/index.mjs
CHANGED
|
@@ -82,6 +82,20 @@ function createStoreClient(opts) {
|
|
|
82
82
|
availability: /* @__PURE__ */ __name((variantRefs) => request(`/store/availability?variantRefs=${enc(variantRefs.join(","))}`), "availability"),
|
|
83
83
|
/** The payment rails this workspace can charge, for rendering your own picker (headless only). */
|
|
84
84
|
paymentMethods: /* @__PURE__ */ __name((currency) => request(`/store/payment-methods${currency ? `?currency=${enc(currency)}` : ""}`), "paymentMethods"),
|
|
85
|
+
// ------------------------------------------ register (fd_pk_ in the browser, or fd_sk_)
|
|
86
|
+
/**
|
|
87
|
+
* Register a guest for a FREE event tier. The one write a publishable key may make, so call it
|
|
88
|
+
* straight from the page. `tickets` has the checkout shape (positional `attendees`, length
|
|
89
|
+
* quantity x groupSize); `responses` answers the tier's `formFields` by `key`. A paid tier is
|
|
90
|
+
* refused with TIER_REQUIRES_PAYMENT: open a checkout for it from your server. Duplicates are
|
|
91
|
+
* quiet (an email that already holds a seat gets the same answer and its confirmation again,
|
|
92
|
+
* never a second seat), so retrying is safe and no idempotency key is needed. FrontDesk sends
|
|
93
|
+
* the confirmation, the .ics invite and the ticket itself.
|
|
94
|
+
*/
|
|
95
|
+
registerForEvent: /* @__PURE__ */ __name((slug, input) => request(`/store/events/${enc(slug)}/register`, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
body: JSON.stringify(input)
|
|
98
|
+
}), "registerForEvent"),
|
|
85
99
|
// --------------------------------------------------- checkout (fd_sk_, server)
|
|
86
100
|
/**
|
|
87
101
|
* Open a hosted checkout. SERVER ONLY — needs a secret key.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontdesk-africa/store-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Typed client for the FrontDesk Storefront API: catalogue, events, forms, hosted checkout and signed webhooks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://api.frontdesk.africa/v1/store/docs",
|