@moonbase.sh/storefront 3.10.0 → 3.11.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 CHANGED
@@ -496,10 +496,43 @@ Available events include:
496
496
  - `downloaded-product`
497
497
  - `activated-product`
498
498
  - `added-to-cart`
499
+ - `coupon-rejected`
499
500
  - `checkout-initiated`
501
+ - `checkout-billing-details-added`
502
+ - `checkout-coupon-applied`
503
+ - `checkout-coupon-removed`
504
+ - `checkout-payment-started`
505
+ - `checkout-payment-failed`
500
506
  - `checkout-closed`
501
507
  - `checkout-completed`
502
508
 
509
+ The checkout's own progress, from billing details to payment, is reported only by the checkout
510
+ that opens on your page:
511
+
512
+ - `checkout-billing-details-added` fires each time the buyer saves their billing details.
513
+ - `checkout-payment-started` fires for each attempt to pay, and is followed by
514
+ `checkout-payment-failed` or `checkout-completed`.
515
+ - `checkout-payment-failed` carries `error`, with `reason` and `paymentMethod` (such as `card`).
516
+ The reason is one of `Declined` (by the card issuer or fraud screening: try another card),
517
+ `AuthenticationFailed` (3-D Secure not passed), `CardDetails` (a wrong number, security code or
518
+ postcode, or an expired card), `ProcessingError` (trying again may work) or `Other`. The card
519
+ issuer's own decline code is never passed on. The checkout stays open for the buyer to try again.
520
+ - `checkout-coupon-applied` and `checkout-coupon-removed` carry the `code`. A code the checkout
521
+ refuses fires `coupon-rejected`, with the `message` the buyer saw.
522
+ - `checkout-closed` carries `completed`, and `checkoutOrder`, the order the checkout was open for.
523
+ Its `order` is the cart, which for a direct purchase is a different order.
524
+
525
+ During a full-page redirect to the hosted checkout (`checkout.redirect`, and on phones by default)
526
+ your page is not loaded, so only `checkout-completed` reaches it, once the buyer is back. To follow
527
+ every checkout, hosted included, subscribe a webhook to `OrderCheckoutInitiated` (billing details
528
+ saved) and `OrderPaymentFailed`.
529
+
530
+ ```ts
531
+ Moonbase.on(MoonbaseEvent.CheckoutPaymentFailed, ({ order, error }) => {
532
+ console.log(`Payment for ${order.id} failed: ${error.reason}`)
533
+ })
534
+ ```
535
+
503
536
  `signed-in` and `signed-up` fire on the page where the customer signs in, including through a link in an email. `signed-out` also fires when the session ends without anyone signing out on the page: in another tab, or elsewhere (for example a password change on another device), which the widget finds out when it next renews the token.
504
537
 
505
538
  ## Calling your own API
@@ -1,5 +1,6 @@
1
1
  import { ActivationRequestFulfillmentType } from '@moonbase.sh/vue';
2
2
  import { CartItem } from '@moonbase.sh/vue';
3
+ import { CheckoutPaymentFailure } from '@moonbase.sh/vue';
3
4
  import { CheckoutRedirectMode } from '@moonbase.sh/vue';
4
5
  import { CurrencyDisplay } from '@moonbase.sh/vue';
5
6
  import { Download } from '@moonbase.sh/vue';
@@ -28,10 +29,7 @@ export declare interface GoogleAnalyticsConfig {
28
29
  }
29
30
 
30
31
  export declare interface GoogleTagManagerConfig {
31
- /**
32
- * Informational/reserved. GTM containers read from `window.dataLayer`
33
- * regardless of which container loaded them, so this is not used at runtime.
34
- */
32
+ /** Not used: GTM reads `window.dataLayer` whichever container loaded it. */
35
33
  containerId?: string;
36
34
  }
37
35
 
@@ -88,6 +86,11 @@ export declare enum MoonbaseEvent {
88
86
  AddedToCart = "added-to-cart",
89
87
  CouponRejected = "coupon-rejected",
90
88
  CheckoutInitiated = "checkout-initiated",
89
+ CheckoutBillingDetailsAdded = "checkout-billing-details-added",
90
+ CheckoutCouponApplied = "checkout-coupon-applied",
91
+ CheckoutCouponRemoved = "checkout-coupon-removed",
92
+ CheckoutPaymentStarted = "checkout-payment-started",
93
+ CheckoutPaymentFailed = "checkout-payment-failed",
91
94
  CheckoutClosed = "checkout-closed",
92
95
  CheckoutCompleted = "checkout-completed"
93
96
  }
@@ -166,6 +169,11 @@ export declare interface MoonbaseEventArgs {
166
169
  };
167
170
  [MoonbaseEvent.CouponRejected]: {
168
171
  code: string;
172
+ /**
173
+ * Why, as the buyer was told, when the checkout refused a code typed into
174
+ * it. Absent for a code passed to the `purchase` intent.
175
+ */
176
+ message?: string;
169
177
  user?: User | null;
170
178
  };
171
179
  [MoonbaseEvent.CheckoutInitiated]: {
@@ -173,8 +181,61 @@ export declare interface MoonbaseEventArgs {
173
181
  total: Money;
174
182
  user?: User | null;
175
183
  };
184
+ /**
185
+ * The buyer saved their billing details in the checkout. Sent again each time
186
+ * they change them. Only from the checkout that opens on the page, not the
187
+ * hosted one a redirect goes to.
188
+ */
189
+ [MoonbaseEvent.CheckoutBillingDetailsAdded]: {
190
+ order: OpenOrder;
191
+ user?: User | null;
192
+ };
193
+ /** The buyer applied a coupon code in the checkout. On-page checkout only. */
194
+ [MoonbaseEvent.CheckoutCouponApplied]: {
195
+ order: OpenOrder;
196
+ code: string;
197
+ user?: User | null;
198
+ };
199
+ /** The buyer took a coupon off the order in the checkout. On-page checkout only. */
200
+ [MoonbaseEvent.CheckoutCouponRemoved]: {
201
+ order: OpenOrder;
202
+ code: string;
203
+ user?: User | null;
204
+ };
205
+ /**
206
+ * The buyer submitted payment in the checkout. Each attempt is followed by
207
+ * `checkout-payment-failed` or `checkout-completed`. On-page checkout only.
208
+ */
209
+ [MoonbaseEvent.CheckoutPaymentStarted]: {
210
+ order: OpenOrder;
211
+ user?: User | null;
212
+ };
213
+ /**
214
+ * An attempt to pay failed: a declined card, or a 3-D Secure check the buyer
215
+ * did not pass. The checkout stays open for them to try again. `error.reason`
216
+ * says what kind of failure it was; the card issuer's own decline code is
217
+ * never passed on. On-page checkout only: for every checkout, hosted
218
+ * included, subscribe a webhook to `OrderPaymentFailed`.
219
+ */
220
+ [MoonbaseEvent.CheckoutPaymentFailed]: {
221
+ order: OpenOrder;
222
+ error: CheckoutPaymentFailure;
223
+ user?: User | null;
224
+ };
176
225
  [MoonbaseEvent.CheckoutClosed]: {
226
+ /**
227
+ * The cart. For a direct purchase (the `purchase` intent, a buy button)
228
+ * this is not the order the checkout was for: read `checkoutOrder`.
229
+ */
177
230
  order: Order;
231
+ /** The order the checkout was open for. Absent if it closed before it loaded. */
232
+ checkoutOrder?: Order;
233
+ /**
234
+ * Whether the checkout completed the order before it closed, the same
235
+ * moment `checkout-completed` fires. An order that was paid but could not
236
+ * be fulfilled is not completed, and fires neither.
237
+ */
238
+ completed: boolean;
178
239
  user?: User | null;
179
240
  };
180
241
  [MoonbaseEvent.CheckoutCompleted]: {
@@ -222,6 +283,7 @@ declare class MoonbaseImpl implements MoonbaseInstance {
222
283
  redeem_voucher(parameters?: MoonbaseIntentArgs['redeem_voucher']): void;
223
284
  view_product(parameters?: MoonbaseIntentArgs['view_product']): void;
224
285
  download_product(parameters?: MoonbaseIntentArgs['download_product']): void;
286
+ view_release_history(parameters?: MoonbaseIntentArgs['view_release_history']): void;
225
287
  activate_product(parameters?: MoonbaseIntentArgs['activate_product']): void;
226
288
  manage_subscription(parameters?: MoonbaseIntentArgs['manage_subscription']): void;
227
289
  view_cart(): void;
@@ -269,6 +331,7 @@ export declare enum MoonbaseIntent {
269
331
  JoinGroup = "join_group",
270
332
  ViewProduct = "view_product",
271
333
  DownloadProduct = "download_product",
334
+ ViewReleaseHistory = "view_release_history",
272
335
  ActivateProduct = "activate_product",
273
336
  ManageSubscription = "manage_subscription",
274
337
  ViewCart = "view_cart",
@@ -349,6 +412,9 @@ export declare interface MoonbaseIntentArgs {
349
412
  version?: string;
350
413
  key?: string;
351
414
  };
415
+ [MoonbaseIntent.ViewReleaseHistory]: {
416
+ product_id: string;
417
+ };
352
418
  [MoonbaseIntent.ActivateProduct]: {
353
419
  token?: string;
354
420
  };
@@ -413,23 +479,10 @@ export declare interface MoonbaseOptions {
413
479
  };
414
480
  };
415
481
  /**
416
- * Merchant-owned customer groups a visitor joins when they use one of these
417
- * forms. Named by ID, because the API deliberately tells a storefront nothing
418
- * about a group: an ID that names a group the merchant has not opened to
419
- * public sign-ups is skipped in silence, so a stale one here can never fail a
420
- * registration or a sale. Enrollment is therefore silent too, and there is
421
- * nothing to label.
422
- *
423
- * `signUp` and `subscribe` can be added to per link with `mb_groups`, e.g.
424
- * `?mb_intent=sign_up&mb_groups=beta,vip`. The two are unioned: the options
425
- * say which lists everyone signing up through this site joins, the link adds
426
- * whichever the campaign is for. A host-page `data-moonbase-form="subscribe"`
427
- * form adds to `subscribe` the same way, with a field named `groups`.
428
- *
429
- * `checkout` is options-only. The cart outlives any one intent, and a buyer
430
- * pressing the drawer's own checkout button never went through a link, so a
431
- * URL-scoped group there would apply or not depending on which button they
432
- * happened to press.
482
+ * IDs of the customer groups a visitor joins through each form. A group not open to public
483
+ * sign-ups is skipped silently. A link adds to `signUp` and `subscribe` with `mb_groups`
484
+ * (`?mb_intent=sign_up&mb_groups=beta,vip`), and a `data-moonbase-form="subscribe"` form with a
485
+ * `groups` field. `checkout` is set here only.
433
486
  */
434
487
  groups: {
435
488
  signUp: string[];
@@ -438,68 +491,36 @@ export declare interface MoonbaseOptions {
438
491
  };
439
492
  checkout: {
440
493
  /**
441
- * Where checkout happens.
442
- *
443
- * - `'auto'` (default) — overlay on desktop, full-page redirect on mobile.
444
- * The redirect is what makes Apple Pay and Google Pay available: those
445
- * wallets require the *top-level* domain to be a registered payment-method
446
- * domain, which it never is when checkout runs in an iframe on the host's
447
- * own site.
448
- * - `'always'` / `true` — always redirect to the hosted checkout.
449
- * - `'never'` / `false` — always use the overlay, wallets included in the cost.
494
+ * Where checkout happens:
495
+ * - `'auto'` (default): an overlay on desktop, the hosted checkout on mobile.
496
+ * - `'always'` / `true`: always the hosted checkout.
497
+ * - `'never'` / `false`: always the overlay, which can't offer Apple Pay or Google Pay.
450
498
  */
451
499
  redirect: boolean | CheckoutRedirectMode;
452
500
  };
453
501
  cart: {
454
502
  quantity: 'selectable' | 'single';
455
503
  bundles: {
504
+ /**
505
+ * `'replace'` (default) takes a bundle's products out of the cart when it is added, if they are
506
+ * sold the same way (subscription or one-off) as the bundle. `'append'` leaves them in.
507
+ */
456
508
  onAdd: 'append' | 'replace';
457
509
  showProducts: boolean;
458
510
  };
459
511
  offers: {
460
512
  label: string;
461
513
  /**
462
- * Apply an eligible offer to the cart line it targets without waiting for the buyer to press
463
- * its upsell card. Applies to offers keyed off a single product; offers keyed off the cart as a
464
- * whole have always applied themselves; see `cartWide` below.
465
- *
466
- * Off by default: an offer is a merchant's to give, and applying one unasked discounts a cart
467
- * the buyer never asked to have discounted. Turn it on to close the one case the upsell cannot
468
- * reach. The card is hidden once the offer's targets are in the cart, so a cart assembled in
469
- * that order (target first, then the item that satisfies the offer's condition) leaves a real
470
- * discount with no way of being claimed.
471
- *
472
- * Never adds anything to the cart, and never takes an offer the cart is still selling: an offer
473
- * that names a product the buyer has not added keeps showing as a card and still takes a press,
474
- * since an offer is good for one line only and spending it early would remove that card. Nor
475
- * does it replace an offer a line already carries.
476
- *
477
- * That last part governs when an offer is taken, not where it stays. Remove one of the products
478
- * that qualified an offer and it stays on the line it landed on, exactly as an offer claimed
479
- * from the card would. A buyer can put a multi-target offer on one product and never add the
480
- * other, so this is a cart they could have built by hand. Losing the discount because of an
481
- * unrelated removal would be the surprising behaviour, not keeping it.
514
+ * Put an eligible single-product offer on the line it targets without the buyer pressing its
515
+ * card, which hides once the target is in the cart. Off by default. Never adds to the cart or
516
+ * replaces an offer a line already has.
482
517
  */
483
518
  autoApply: boolean;
484
- /**
485
- * Offers keyed off the cart as a whole, such as "10% off orders over $50", rather than off a single
486
- * product. They apply themselves the moment the cart qualifies; there is nothing for the buyer
487
- * to press.
488
- */
519
+ /** Offers on the whole cart, such as "10% off orders over $50". They apply once the cart qualifies. */
489
520
  cartWide: {
490
- /**
491
- * Show the eligibility meter in the cart: how close the cart is to the offer's threshold
492
- * while it is short, and the discount confirmed once it is not.
493
- *
494
- * Set to `false` to let the offer apply silently: the saving still shows in the totals, the
495
- * nudge does not. With no such offer configured the meter never renders either way.
496
- */
521
+ /** Show how far the cart is from the offer's threshold. `false` applies it without the meter. */
497
522
  showProgress: boolean;
498
- /**
499
- * Label for the saving row in the cart totals, above `Total`. Kept in the widget's own voice
500
- * rather than taking the offer's name, so the totals column can't be pushed around by
501
- * merchant-length copy.
502
- */
523
+ /** Label for the saving row in the cart totals. */
503
524
  savingLabel: string;
504
525
  };
505
526
  };
@@ -507,19 +528,10 @@ export declare interface MoonbaseOptions {
507
528
  quantity: 'selectable' | 'single';
508
529
  };
509
530
  /**
510
- * The smallest order the cart will check out, keyed by currency code in major units, e.g.
511
- * `{ USD: 25, EUR: 25 }`. Below it the checkout button gives way to a meter showing how much
512
- * further the buyer has to go, and the `checkout` intent opens the cart instead of checking out.
513
- *
514
- * Per currency because the API picks each visitor's currency and the widget never converts one
515
- * into another. A currency with no entry has no minimum, so a buyer priced in one you did not
516
- * list checks out as before.
517
- *
518
- * Measured on the cart's own Total: after product discounts and offers, before coupons and tax.
519
- * A cart of nothing but free items is exempt, since there is no purchase to hold back.
520
- *
521
- * The widget enforces this, not the API: `purchase()` and checkouts built outside the widget are
522
- * not held to it. A value you pass replaces the previous one outright; pass `{}` to remove it.
531
+ * The smallest order the cart checks out, per currency in major units, e.g. `{ USD: 25, EUR: 25 }`.
532
+ * Measured after discounts and offers, before coupons and tax. A currency left out has no
533
+ * minimum, and a cart of only free items is exempt. `purchase()` is not held to it. Pass `{}` to
534
+ * remove it.
523
535
  */
524
536
  minimumTotal: Record<string, number>;
525
537
  };
@@ -533,22 +545,13 @@ export declare interface MoonbaseOptions {
533
545
  };
534
546
  promotions: {
535
547
  /**
536
- * Whether to render the sales a merchant is running. When a promotion is live, the widget
537
- * surfaces it as a banner and/or a popup depending on what the merchant asked for. Turn this
538
- * off to render the campaign yourself from `Moonbase.on('promotion-shown', …)` or the
539
- * `usePromotions()` composable in `@moonbase.sh/vue`.
548
+ * Show running promotions as a banner or popup. Turn off to render them yourself from the
549
+ * `promotion-shown` event or `usePromotions()` in `@moonbase.sh/vue`.
540
550
  */
541
551
  enabled: boolean;
542
- /**
543
- * Where the banner sits when the host page has no `[data-moonbase-promotion]` container for
544
- * it. Ignored when one exists — then the banner renders inline, wherever you put it.
545
- */
552
+ /** Where the banner goes when the page has no `[data-moonbase-promotion]` container. */
546
553
  location: 'top' | 'bottom';
547
- /**
548
- * The label on the dismiss action, and the primary action when a sale has no destination. The
549
- * wording for a sale that *does* lead somewhere comes from the promotion itself, since only
550
- * the merchant knows what the campaign is offering.
551
- */
554
+ /** Label for dismissing a promotion, and its only action when it has no link. */
552
555
  dismissLabel: string;
553
556
  };
554
557
  theme: {
@@ -559,19 +562,11 @@ export declare interface MoonbaseOptions {
559
562
  };
560
563
  fonts: {
561
564
  /**
562
- * Font for headings and display text. Either a built-in family, which the widget loads from
563
- * Moonbase's own asset host, or one of:
564
- *
565
- * - `'inherit'` — render in whatever font the host page uses at that spot. Loads nothing.
566
- * - `'system'` — the widget's own system stack. Loads nothing.
567
- * - `{ family }` — name any family the page already loads.
568
- * - `{ family, stylesheet }` — name any family and the URL to load the face from.
569
- *
570
- * The distinction that catches people out: `'Inter'` means "load Inter for me",
571
- * `{ family: 'Inter' }` means "the family is Inter, I already load it".
565
+ * A built-in family the widget loads (`'Inter'`), `'inherit'` for the page's font, `'system'`,
566
+ * `{ family }` for one the page already loads, or `{ family, stylesheet }` to load one.
572
567
  */
573
568
  heading: MoonbaseFont;
574
- /** As `heading`, for body copy. Either slot accepts any of the built-in families. */
569
+ /** As `heading`, for body text. */
575
570
  body: MoonbaseFont;
576
571
  };
577
572
  corners: 'sharp' | 'soft' | 'round';
@@ -579,44 +574,17 @@ export declare interface MoonbaseOptions {
579
574
  cards: 'outlined' | 'shadow' | 'white';
580
575
  };
581
576
  pricing: {
582
- /**
583
- * How to render a price that lands on a whole number.
584
- * - `'auto'` (default) drops the fraction entirely, so `10` renders `$10`.
585
- * - `'always'` keeps the currency's own fraction digits, so `10` renders
586
- * `$10.00` — and still `¥1000` for zero-decimal currencies like JPY.
587
- *
588
- * Prices that aren't whole are unaffected; they always show their decimals.
589
- */
577
+ /** Whole prices: `'auto'` (default) shows `$10`, `'always'` shows `$10.00`. */
590
578
  trailingZeros: TrailingZeros;
591
- /**
592
- * Which marker identifies the currency. Taking `10` USD read in a British
593
- * locale:
594
- * - `'narrowSymbol'` (default) renders the shortest symbol, `$10`.
595
- * - `'symbol'` renders the locale's usual symbol, `US$10`.
596
- * - `'code'` renders the ISO 4217 code, `USD 10`.
597
- * - `'name'` spells it out in the visitor's language, `10 US dollars`.
598
- *
599
- * The symbol and its placement always follow the visitor's own locale;
600
- * this only picks which of the four forms to use.
601
- */
579
+ /** `'narrowSymbol'` (default) `$10`, `'symbol'` `US$10`, `'code'` `USD 10`, `'name'` `10 US dollars`. */
602
580
  currencyDisplay: CurrencyDisplay;
603
581
  };
604
582
  target: HTMLElement | undefined;
605
- /**
606
- * When the host page declares a fixed-width mobile viewport (e.g. Wix's
607
- * legacy `<meta name="viewport" content="width=320, ...">`), the browser
608
- * upscales every CSS pixel to fit the device. Set this to `true` to opt out
609
- * of the widget's automatic counter-scaling and match the host's density.
610
- */
583
+ /** Turn off the widget's scaling on pages with a fixed-width mobile viewport, such as Wix's `width=320`. */
611
584
  disableViewportCompensation: boolean;
612
585
  /**
613
- * Opt in to built-in forwarding of storefront events to common
614
- * marketing/analytics SDKs. Each provider is enabled by supplying its
615
- * config object. The widget never loads SDK scripts — it only forwards
616
- * to globals the host page has already initialised (e.g. `window.fbq`,
617
- * `window.gtag`, `window.dataLayer`, `window.klaviyo`, `window.ttq`).
618
- * If a provider's global is missing at the time of an event, the
619
- * forwarding is a silent no-op.
586
+ * Forward storefront events to analytics SDKs the page already loads (`fbq`, `gtag`,
587
+ * `dataLayer`, `klaviyo`, `ttq`). Pass a provider's config to turn it on.
620
588
  */
621
589
  integrations: IntegrationsConfig;
622
590
  }