@shoppexio/storefront-react 0.5.0 → 0.6.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/index.d.ts CHANGED
@@ -558,6 +558,35 @@ interface SdkCustomerSubscription {
558
558
  } | null;
559
559
  /** The Discord roles a pause takes ([] none); null when not known. Optional for deploy order. */
560
560
  pause_access_roles?: string[] | null;
561
+ /** The running coupon term; null without one. Optional for deploy order. */
562
+ discount?: SdkCustomerSubscriptionDiscount | null;
563
+ }
564
+ /**
565
+ * `SubscriptionCouponTermView` on the dashboard wire: the code, what it takes off each payment,
566
+ * the payments it still discounts, and what the renewals cost. Every amount is the renewal-pricing
567
+ * function's, in `currency`.
568
+ */
569
+ interface SdkCustomerSubscriptionDiscount {
570
+ code: string;
571
+ discount_type: 'PERCENTAGE' | 'FIXED';
572
+ /** The percent, or the fixed amount. */
573
+ discount_value: string;
574
+ /** The cap on one payment's discount; null without one. */
575
+ max_discount: string | null;
576
+ currency: string;
577
+ duration: 'ONCE' | 'REPEATING' | 'FOREVER';
578
+ /** ONCE: 1, REPEATING: N, FOREVER: null (every payment). */
579
+ payments_total: number | null;
580
+ /** Discounted payments completed; an open or voided renewal does not count. */
581
+ payments_used: number;
582
+ /** A discounted renewal. */
583
+ discounted_amount: string;
584
+ /** A renewal at the plan price, once the term ends. */
585
+ full_amount: string;
586
+ /** What each discounted renewal saves. */
587
+ discount_amount: string;
588
+ /** What the next renewal charges: `discounted_amount`, or `full_amount` once every discounted payment is issued. */
589
+ next_amount: string;
561
590
  }
562
591
  /** Why a buyer pauses (`SUBSCRIPTION_PAUSE_REASONS`). */
563
592
  type SdkCustomerPauseReason = 'taking_a_break' | 'too_expensive' | 'not_using' | 'not_working' | 'other';
@@ -3705,4 +3734,4 @@ declare function useOnlineUserCount(): number | null;
3705
3734
  */
3706
3735
  declare function resolveProductVideoEmbedSrc(value: unknown): string | null;
3707
3736
 
3708
- export { ACCOUNT_ARTIFACT_CAPABILITIES, ACCOUNT_PATH, ACCOUNT_PATH_ALIAS, ACCOUNT_ROUTE_MESSAGE_KEYS, ACCOUNT_STOREFRONT_ROUTES, ACCOUNT_TABS, type AccountRouteMessageKey, type AccountRouteParts, type AccountTab, type AccountView, type BuyBoxVariantModel, CATALOG_PAGE_SIZE, CATALOG_SORTS, CATALOG_SORT_LABELS, type CartBoundsHeal, CartProvider, type CartQuoteLineBounds, type CartTotals, type CartView, type CartViewLine, type CatalogCardModel, type CatalogPage, type CatalogSort, type CheckoutFieldGroup, type CheckoutFieldValues, type CheckoutFields, type ContactFormValues, type ContactSubmissionContext, DEFAULT_ACCOUNT_TAB, DEFAULT_LOW_STOCK_THRESHOLD, DELIVERY_TIME_UNITS, type DeliveryPromise, type DeliveryTimeUnit, FAVORITES_CHANGED_EVENT, type FavoriteToggleResult, type FavoritesApi, FavoritesProvider, type FavoritesSnapshot, type FavoritesStatus, type FavoritesStore, type FavoritesTransport, type GroupCardModel, IMPLEMENTED_ACCOUNT_TABS, type ImplementedAccountTab, type LicenseStatement, type LicenseTerm, type LinePricing, MAX_PAY_WHAT_YOU_WANT_PRICE, type MenuNavLink, type MenuSlot, type NavLink, type NavigationLinks, PRODUCT_BADGE_ICON_NAMES, type PagedShopReviewsState, type ProductAddon, type ProductAutomaticDeal, type ProductBadge, type ProductBadgeIconName, type ProductBundle, type ProductBundleItem, type ProductCardModel, type ProductCategoryRef, type ProductContentTab, type ProductCustomField, type ProductDeliverable, type ProductDeliveryType, type ProductDetailModel, type ProductFaq, type ProductImage, type ProductImageKind, type ProductRatingModel, type ProductServiceStatus, type ProductSubscription, type ProductType, QUOTE_UNAVAILABLE_CHECKOUT_ERROR, type RatingStars, type ResellerProgramSummary, type RewardProgramItem, type RewardProgramSummary, SERVICE_STATES, SERVING_CONTEXT_HINT, SHOP_REVIEW_PAGE_SIZE, STOREFRONT_SORT_KEYS, type SdkCartAddOptions, type SdkCartAddon, type SdkCartAppliedDiscount, type SdkCartCodeSource, type SdkCartItem, type SdkCartQuote, type SdkCartQuoteLine, type SdkCheckoutResult, type SdkCustomerAffiliateLedgerStatus, type SdkCustomerAffiliateLink, type SdkCustomerAffiliateLinkCreated, type SdkCustomerAffiliateRates, type SdkCustomerAffiliateSummary, type SdkCustomerDashboard, type SdkCustomerDashboardOrder, type SdkCustomerDeliverable, type SdkCustomerDeliveryStatus, type SdkCustomerDeliverySummary, type SdkCustomerFileAccess, type SdkCustomerInvoicePdf, type SdkCustomerLicense, type SdkCustomerLoyalty, type SdkCustomerLoyaltyRedeem, type SdkCustomerLoyaltyTransactionType, type SdkCustomerNotificationPreferences, type SdkCustomerOpenRenewal, type SdkCustomerOrderDetail, type SdkCustomerOrderPage, type SdkCustomerOrderReview, type SdkCustomerOrderSummary, type SdkCustomerPause, type SdkCustomerPauseOptions, type SdkCustomerPauseReason, type SdkCustomerRewardActivity, type SdkCustomerRewards, type SdkCustomerSubscription, type SdkCustomerTicketSummary, type SdkCustomerWarranty, type SdkCustomerWarrantyClaim, type SdkCustomerWarrantyClaimStatus, type SdkCustomerWarrantyList, type SdkCustomerWarrantyStatus, type SdkEligibleCartDeal, type SdkInitOptions, type SdkMenu, type SdkMenuItem, type SdkPage, type SdkRecentSaleEntry, type SdkResponse, type SdkShopReview, type SdkShopReviewsSummary, type ServiceState, type ShopPageFaqItem, type ShopPageModel, type ShopPageState, type ShopReview, type ShopReviewLabels, type ShopReviewsPageResult, type ShopReviewsState, type ShoppexSdk, type SocialLink, type SocialNetwork, type StatusPageEntry, type StatusPageModel, type StockAlertRequest, type StockAlertResult, type StockChip, type StockTone, type StorefrontAddon, type StorefrontCatalog, type StorefrontCategory, type StorefrontData, type StorefrontGroup, type StorefrontListingItem, type StorefrontPaymentMethod, type StorefrontPaymentMethodKind, type StorefrontPriceVariant, type StorefrontProduct, StorefrontProvider, type StorefrontSettings, type StorefrontShop, type StorefrontShopStats, type StorefrontSortKey, type SubscriptionIntervalUnit, type SubscriptionTrial, type SupportChannel, type VolumeDiscountRule, type YouReceive, accountOrderHref, accountRouteMessage, accountTabHref, accountTabLabelKey, addonChoiceMissing, averageRating, badgesForCard, badgesForProductPage, buildCartTotals, buildCartView, buildCustomFieldPayload, buildInitialCheckoutFieldValues, buildProductDetail, buildStatusPageModel, buildStorefrontCatalog, calculateDisplayLineTotal, calculateLinePricing, canRequestStockAlert, canonicalAccountPath, checkoutFieldKey, checkoutFieldWriteRepriced, checkoutNeedsDetails, clampQuantity, clearRecoveredQuoteCheckoutError, createFavoritesStore, filterCatalogCards, findStorefrontProduct, firstOptionalString, flattenMerchantHtmlToText, formatCompactCount, formatDeliveryTime, formatLicenseTerm, formatMoney, formatSubscriptionCadence, formatSubscriptionTrial, getAuthoredContent, getBuyerCurrency, getProductGroups, getProducts, getShoppex, getStorefrontAddons, getStorefrontCategories, getStorefrontListing, getStorefrontSettings, getStorefrontShop, hasInjectedStorefrontData, isAccountTab, isBlankMerchantHtml, isCardSoldOut, isCheckboxValueChecked, isImplementedAccountTab, isProductSoldOut, isRecord, isSelectionSoldOut, loadLatestWrittenReviews, loadShopPage, loadShopReviewsPage, missingRequiredCheckoutValues, notifyFavoritesChanged, optionalBoolean, optionalString, optionalStringWithDefault, paginateCatalogCards, parseAccountPath, parseCartQuoteLines, parseEnum, parseFiniteNumber, parseProductBadges, parseServiceStatus, parseStorefrontSortKey, readCheckoutNeedsDetails, readStorefrontData, requestStockAlert, requireArray, requireBoolean, requireNumber, requireRecord, requireString, requireStringFromKeys, requireUnknownRecord, resolveAccountView, resolveBadgeForeground, resolveCartBoundsHeals, resolveCategorySelection, resolveCheckoutFields, resolveDeliveryPromise, resolveDisplayStock, resolveHideStockCounter, resolveLicenseTerm, resolveMenuLinksForSlot, resolvePayWhatYouWantMinimum, resolveProductVideoEmbedSrc, resolveQuantityBounds, resolveSupportChannels, resolveUnsellableLineIds, resolveVariantStock, resolveYouReceive, reviewsForProduct, roundCurrency, sanitizeMerchantHtml, sdkFavoritesTransport, sortCatalogCards, startCartMutation, stripMerchantHtmlImages, submitContactTicket, subscriptionForVariant, toShopReviews, toStarCount, useCart, useFavorites, useNavigationLinks, useOnlineUserCount, usePagedShopReviews, useShopReviews, useStorefront, validateCheckoutFieldValue, validateCheckoutFields, validateContactSubmission, withRequiredAddons };
3737
+ export { ACCOUNT_ARTIFACT_CAPABILITIES, ACCOUNT_PATH, ACCOUNT_PATH_ALIAS, ACCOUNT_ROUTE_MESSAGE_KEYS, ACCOUNT_STOREFRONT_ROUTES, ACCOUNT_TABS, type AccountRouteMessageKey, type AccountRouteParts, type AccountTab, type AccountView, type BuyBoxVariantModel, CATALOG_PAGE_SIZE, CATALOG_SORTS, CATALOG_SORT_LABELS, type CartBoundsHeal, CartProvider, type CartQuoteLineBounds, type CartTotals, type CartView, type CartViewLine, type CatalogCardModel, type CatalogPage, type CatalogSort, type CheckoutFieldGroup, type CheckoutFieldValues, type CheckoutFields, type ContactFormValues, type ContactSubmissionContext, DEFAULT_ACCOUNT_TAB, DEFAULT_LOW_STOCK_THRESHOLD, DELIVERY_TIME_UNITS, type DeliveryPromise, type DeliveryTimeUnit, FAVORITES_CHANGED_EVENT, type FavoriteToggleResult, type FavoritesApi, FavoritesProvider, type FavoritesSnapshot, type FavoritesStatus, type FavoritesStore, type FavoritesTransport, type GroupCardModel, IMPLEMENTED_ACCOUNT_TABS, type ImplementedAccountTab, type LicenseStatement, type LicenseTerm, type LinePricing, MAX_PAY_WHAT_YOU_WANT_PRICE, type MenuNavLink, type MenuSlot, type NavLink, type NavigationLinks, PRODUCT_BADGE_ICON_NAMES, type PagedShopReviewsState, type ProductAddon, type ProductAutomaticDeal, type ProductBadge, type ProductBadgeIconName, type ProductBundle, type ProductBundleItem, type ProductCardModel, type ProductCategoryRef, type ProductContentTab, type ProductCustomField, type ProductDeliverable, type ProductDeliveryType, type ProductDetailModel, type ProductFaq, type ProductImage, type ProductImageKind, type ProductRatingModel, type ProductServiceStatus, type ProductSubscription, type ProductType, QUOTE_UNAVAILABLE_CHECKOUT_ERROR, type RatingStars, type ResellerProgramSummary, type RewardProgramItem, type RewardProgramSummary, SERVICE_STATES, SERVING_CONTEXT_HINT, SHOP_REVIEW_PAGE_SIZE, STOREFRONT_SORT_KEYS, type SdkCartAddOptions, type SdkCartAddon, type SdkCartAppliedDiscount, type SdkCartCodeSource, type SdkCartItem, type SdkCartQuote, type SdkCartQuoteLine, type SdkCheckoutResult, type SdkCustomerAffiliateLedgerStatus, type SdkCustomerAffiliateLink, type SdkCustomerAffiliateLinkCreated, type SdkCustomerAffiliateRates, type SdkCustomerAffiliateSummary, type SdkCustomerDashboard, type SdkCustomerDashboardOrder, type SdkCustomerDeliverable, type SdkCustomerDeliveryStatus, type SdkCustomerDeliverySummary, type SdkCustomerFileAccess, type SdkCustomerInvoicePdf, type SdkCustomerLicense, type SdkCustomerLoyalty, type SdkCustomerLoyaltyRedeem, type SdkCustomerLoyaltyTransactionType, type SdkCustomerNotificationPreferences, type SdkCustomerOpenRenewal, type SdkCustomerOrderDetail, type SdkCustomerOrderPage, type SdkCustomerOrderReview, type SdkCustomerOrderSummary, type SdkCustomerPause, type SdkCustomerPauseOptions, type SdkCustomerPauseReason, type SdkCustomerRewardActivity, type SdkCustomerRewards, type SdkCustomerSubscription, type SdkCustomerSubscriptionDiscount, type SdkCustomerTicketSummary, type SdkCustomerWarranty, type SdkCustomerWarrantyClaim, type SdkCustomerWarrantyClaimStatus, type SdkCustomerWarrantyList, type SdkCustomerWarrantyStatus, type SdkEligibleCartDeal, type SdkInitOptions, type SdkMenu, type SdkMenuItem, type SdkPage, type SdkRecentSaleEntry, type SdkResponse, type SdkShopReview, type SdkShopReviewsSummary, type ServiceState, type ShopPageFaqItem, type ShopPageModel, type ShopPageState, type ShopReview, type ShopReviewLabels, type ShopReviewsPageResult, type ShopReviewsState, type ShoppexSdk, type SocialLink, type SocialNetwork, type StatusPageEntry, type StatusPageModel, type StockAlertRequest, type StockAlertResult, type StockChip, type StockTone, type StorefrontAddon, type StorefrontCatalog, type StorefrontCategory, type StorefrontData, type StorefrontGroup, type StorefrontListingItem, type StorefrontPaymentMethod, type StorefrontPaymentMethodKind, type StorefrontPriceVariant, type StorefrontProduct, StorefrontProvider, type StorefrontSettings, type StorefrontShop, type StorefrontShopStats, type StorefrontSortKey, type SubscriptionIntervalUnit, type SubscriptionTrial, type SupportChannel, type VolumeDiscountRule, type YouReceive, accountOrderHref, accountRouteMessage, accountTabHref, accountTabLabelKey, addonChoiceMissing, averageRating, badgesForCard, badgesForProductPage, buildCartTotals, buildCartView, buildCustomFieldPayload, buildInitialCheckoutFieldValues, buildProductDetail, buildStatusPageModel, buildStorefrontCatalog, calculateDisplayLineTotal, calculateLinePricing, canRequestStockAlert, canonicalAccountPath, checkoutFieldKey, checkoutFieldWriteRepriced, checkoutNeedsDetails, clampQuantity, clearRecoveredQuoteCheckoutError, createFavoritesStore, filterCatalogCards, findStorefrontProduct, firstOptionalString, flattenMerchantHtmlToText, formatCompactCount, formatDeliveryTime, formatLicenseTerm, formatMoney, formatSubscriptionCadence, formatSubscriptionTrial, getAuthoredContent, getBuyerCurrency, getProductGroups, getProducts, getShoppex, getStorefrontAddons, getStorefrontCategories, getStorefrontListing, getStorefrontSettings, getStorefrontShop, hasInjectedStorefrontData, isAccountTab, isBlankMerchantHtml, isCardSoldOut, isCheckboxValueChecked, isImplementedAccountTab, isProductSoldOut, isRecord, isSelectionSoldOut, loadLatestWrittenReviews, loadShopPage, loadShopReviewsPage, missingRequiredCheckoutValues, notifyFavoritesChanged, optionalBoolean, optionalString, optionalStringWithDefault, paginateCatalogCards, parseAccountPath, parseCartQuoteLines, parseEnum, parseFiniteNumber, parseProductBadges, parseServiceStatus, parseStorefrontSortKey, readCheckoutNeedsDetails, readStorefrontData, requestStockAlert, requireArray, requireBoolean, requireNumber, requireRecord, requireString, requireStringFromKeys, requireUnknownRecord, resolveAccountView, resolveBadgeForeground, resolveCartBoundsHeals, resolveCategorySelection, resolveCheckoutFields, resolveDeliveryPromise, resolveDisplayStock, resolveHideStockCounter, resolveLicenseTerm, resolveMenuLinksForSlot, resolvePayWhatYouWantMinimum, resolveProductVideoEmbedSrc, resolveQuantityBounds, resolveSupportChannels, resolveUnsellableLineIds, resolveVariantStock, resolveYouReceive, reviewsForProduct, roundCurrency, sanitizeMerchantHtml, sdkFavoritesTransport, sortCatalogCards, startCartMutation, stripMerchantHtmlImages, submitContactTicket, subscriptionForVariant, toShopReviews, toStarCount, useCart, useFavorites, useNavigationLinks, useOnlineUserCount, usePagedShopReviews, useShopReviews, useStorefront, validateCheckoutFieldValue, validateCheckoutFields, validateContactSubmission, withRequiredAddons };
@@ -95,6 +95,11 @@ export function paymentMethodLabel(payment: PaymentMethodSource): string {
95
95
  if (payment.apmMethod) return payment.apmMethod;
96
96
  // Crypto payments show the concrete coin; the rail stays internal.
97
97
  if (payment.cryptoGateway) return cryptoCoinLabel(payment.cryptoGateway);
98
- const base = GATEWAY_LABELS[payment.gateway] ?? payment.gateway;
98
+ const base = gatewayLabel(payment.gateway);
99
99
  return payment.blockchain ? `${base} (${payment.blockchain})` : base;
100
100
  }
101
+
102
+ /** The rail in the words checkout shows it to the buyer (`PAYPAL_FF` → `PayPal F&F`). */
103
+ export function gatewayLabel(gateway: string): string {
104
+ return GATEWAY_LABELS[gateway] ?? gateway;
105
+ }
@@ -84,9 +84,11 @@ const KNOWN_INTERVALS: ReadonlySet<string> = new Set(['day', 'week', 'month', 'y
84
84
  * it: "every 1 months" is wrong in English and worse in the languages that
85
85
  * decline the noun. An interval the gateway spells in a way this catalog does
86
86
  * not know keeps the raw word instead of being renamed into one that is close.
87
+ * `period` words the same interval after a price ("a month", "every 3 months").
87
88
  */
88
89
  export function billingMessage(
89
90
  plan: NonNullable<SdkCustomerSubscription['plan']>,
91
+ kind: 'billing' | 'period' = 'billing',
90
92
  ): { key: string; params: { count: number; interval: string } } {
91
93
  const interval = plan.interval.trim().toLowerCase();
92
94
  // A gateway that reports 0 or a negative count still bills once per interval.
@@ -94,12 +96,12 @@ export function billingMessage(
94
96
  const params = { count, interval: plan.interval };
95
97
 
96
98
  if (!KNOWN_INTERVALS.has(interval)) {
97
- return { key: 'subscriptions.billingOther', params };
99
+ return { key: `subscriptions.${kind}Other`, params };
98
100
  }
99
101
 
100
102
  const suffix = count === 1 ? 'One' : 'Other';
101
103
  const name = interval.charAt(0).toUpperCase() + interval.slice(1);
102
- return { key: `subscriptions.billing${name}${suffix}`, params };
104
+ return { key: `subscriptions.${kind}${name}${suffix}`, params };
103
105
  }
104
106
 
105
107
  /**
@@ -263,6 +263,21 @@
263
263
  "billingOther": "فوترة كل {count} × {interval}",
264
264
  "gateway": "مزوّد الدفع",
265
265
  "paymentMethod": "طريقة الدفع",
266
+ "discount": "الخصم",
267
+ "discountPercent": "{percent}%",
268
+ "discountCap": "، بحد أقصى {amount} لكل دفعة",
269
+ "discountEvery": "{code} · خصم {off} على كل دفعة{cap}",
270
+ "discountLeft": "{code} · خصم {off}{cap} · الدفعات المتبقية: {count}، ثم {price} {period}",
271
+ "discountLeftOne": "{code} · خصم {off}{cap} · دفعة واحدة متبقية، ثم {price} {period}",
272
+ "periodDayOne": "يوميًا",
273
+ "periodDayOther": "كل {count} أيام",
274
+ "periodWeekOne": "أسبوعيًا",
275
+ "periodWeekOther": "كل {count} أسابيع",
276
+ "periodMonthOne": "شهريًا",
277
+ "periodMonthOther": "كل {count} أشهر",
278
+ "periodYearOne": "سنويًا",
279
+ "periodYearOther": "كل {count} سنوات",
280
+ "periodOther": "كل {count} {interval}",
266
281
  "cardOnFile": "{brand} المنتهية بالأرقام {last4}",
267
282
  "noPaymentMethod": "لا توجد طريقة محفوظة",
268
283
  "pause": "إيقاف مؤقت",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "Abrechnung alle {count} × {interval}",
264
264
  "gateway": "Zahlungsanbieter",
265
265
  "paymentMethod": "Zahlungsmethode",
266
+ "discount": "Rabatt",
267
+ "discountPercent": "{percent} %",
268
+ "discountCap": ", höchstens {amount} pro Zahlung",
269
+ "discountEvery": "{code} · {off} Rabatt auf jede Zahlung{cap}",
270
+ "discountLeft": "{code} · {off} Rabatt{cap} · noch {count} Zahlungen, danach {price} {period}",
271
+ "discountLeftOne": "{code} · {off} Rabatt{cap} · noch 1 Zahlung, danach {price} {period}",
272
+ "periodDayOne": "pro Tag",
273
+ "periodDayOther": "alle {count} Tage",
274
+ "periodWeekOne": "pro Woche",
275
+ "periodWeekOther": "alle {count} Wochen",
276
+ "periodMonthOne": "pro Monat",
277
+ "periodMonthOther": "alle {count} Monate",
278
+ "periodYearOne": "pro Jahr",
279
+ "periodYearOther": "alle {count} Jahre",
280
+ "periodOther": "alle {count} {interval}",
266
281
  "cardOnFile": "{brand} endet auf {last4}",
267
282
  "noPaymentMethod": "Keine hinterlegt",
268
283
  "pause": "Pausieren",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "Billed every {count} × {interval}",
264
264
  "gateway": "Payment provider",
265
265
  "paymentMethod": "Payment method",
266
+ "discount": "Discount",
267
+ "discountPercent": "{percent} %",
268
+ "discountCap": ", up to {amount} each",
269
+ "discountEvery": "{code} · {off} off every payment{cap}",
270
+ "discountLeft": "{code} · {off} off{cap} · {count} more payments, then {price} {period}",
271
+ "discountLeftOne": "{code} · {off} off{cap} · 1 more payment, then {price} {period}",
272
+ "periodDayOne": "a day",
273
+ "periodDayOther": "every {count} days",
274
+ "periodWeekOne": "a week",
275
+ "periodWeekOther": "every {count} weeks",
276
+ "periodMonthOne": "a month",
277
+ "periodMonthOther": "every {count} months",
278
+ "periodYearOne": "a year",
279
+ "periodYearOther": "every {count} years",
280
+ "periodOther": "every {count} {interval}",
266
281
  "cardOnFile": "{brand} ending {last4}",
267
282
  "noPaymentMethod": "None on file",
268
283
  "pause": "Pause",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "Se cobra cada {count} × {interval}",
264
264
  "gateway": "Proveedor de pago",
265
265
  "paymentMethod": "Método de pago",
266
+ "discount": "Descuento",
267
+ "discountPercent": "{percent} %",
268
+ "discountCap": ", hasta {amount} por pago",
269
+ "discountEvery": "{code} · {off} de descuento en cada pago{cap}",
270
+ "discountLeft": "{code} · {off} de descuento{cap} · {count} pagos más, después {price} {period}",
271
+ "discountLeftOne": "{code} · {off} de descuento{cap} · 1 pago más, después {price} {period}",
272
+ "periodDayOne": "al día",
273
+ "periodDayOther": "cada {count} días",
274
+ "periodWeekOne": "a la semana",
275
+ "periodWeekOther": "cada {count} semanas",
276
+ "periodMonthOne": "al mes",
277
+ "periodMonthOther": "cada {count} meses",
278
+ "periodYearOne": "al año",
279
+ "periodYearOther": "cada {count} años",
280
+ "periodOther": "cada {count} {interval}",
266
281
  "cardOnFile": "{brand} terminada en {last4}",
267
282
  "noPaymentMethod": "Ninguno guardado",
268
283
  "pause": "Pausar",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "Laskutetaan {count} × {interval} välein",
264
264
  "gateway": "Maksupalvelu",
265
265
  "paymentMethod": "Maksutapa",
266
+ "discount": "Alennus",
267
+ "discountPercent": "{percent} %",
268
+ "discountCap": ", enintään {amount} maksua kohden",
269
+ "discountEvery": "{code} · {off} alennus jokaisesta maksusta{cap}",
270
+ "discountLeft": "{code} · {off} alennus{cap} · vielä {count} maksua, sitten {price} {period}",
271
+ "discountLeftOne": "{code} · {off} alennus{cap} · vielä 1 maksu, sitten {price} {period}",
272
+ "periodDayOne": "päivässä",
273
+ "periodDayOther": "{count} päivän välein",
274
+ "periodWeekOne": "viikossa",
275
+ "periodWeekOther": "{count} viikon välein",
276
+ "periodMonthOne": "kuukaudessa",
277
+ "periodMonthOther": "{count} kuukauden välein",
278
+ "periodYearOne": "vuodessa",
279
+ "periodYearOther": "{count} vuoden välein",
280
+ "periodOther": "{count} {interval} välein",
266
281
  "cardOnFile": "{brand}, päättyy numeroihin {last4}",
267
282
  "noPaymentMethod": "Ei tallennettuna",
268
283
  "pause": "Keskeytä",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "Facturé tous les {count} × {interval}",
264
264
  "gateway": "Prestataire de paiement",
265
265
  "paymentMethod": "Moyen de paiement",
266
+ "discount": "Réduction",
267
+ "discountPercent": "{percent} %",
268
+ "discountCap": ", jusqu’à {amount} par paiement",
269
+ "discountEvery": "{code} · {off} de réduction sur chaque paiement{cap}",
270
+ "discountLeft": "{code} · {off} de réduction{cap} · encore {count} paiements, puis {price} {period}",
271
+ "discountLeftOne": "{code} · {off} de réduction{cap} · encore 1 paiement, puis {price} {period}",
272
+ "periodDayOne": "par jour",
273
+ "periodDayOther": "tous les {count} jours",
274
+ "periodWeekOne": "par semaine",
275
+ "periodWeekOther": "toutes les {count} semaines",
276
+ "periodMonthOne": "par mois",
277
+ "periodMonthOther": "tous les {count} mois",
278
+ "periodYearOne": "par an",
279
+ "periodYearOther": "tous les {count} ans",
280
+ "periodOther": "tous les {count} {interval}",
266
281
  "cardOnFile": "{brand} finissant par {last4}",
267
282
  "noPaymentMethod": "Aucun enregistré",
268
283
  "pause": "Mettre en pause",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "हर {count} × {interval} में बिलिंग",
264
264
  "gateway": "भुगतान प्रदाता",
265
265
  "paymentMethod": "भुगतान का तरीका",
266
+ "discount": "छूट",
267
+ "discountPercent": "{percent}%",
268
+ "discountCap": ", हर भुगतान पर अधिकतम {amount}",
269
+ "discountEvery": "{code} · हर भुगतान पर {off} की छूट{cap}",
270
+ "discountLeft": "{code} · {off} की छूट{cap} · {count} और भुगतान, फिर {price} {period}",
271
+ "discountLeftOne": "{code} · {off} की छूट{cap} · 1 और भुगतान, फिर {price} {period}",
272
+ "periodDayOne": "प्रति दिन",
273
+ "periodDayOther": "हर {count} दिन",
274
+ "periodWeekOne": "प्रति सप्ताह",
275
+ "periodWeekOther": "हर {count} सप्ताह",
276
+ "periodMonthOne": "प्रति माह",
277
+ "periodMonthOther": "हर {count} महीने",
278
+ "periodYearOne": "प्रति वर्ष",
279
+ "periodYearOther": "हर {count} साल",
280
+ "periodOther": "हर {count} {interval}",
266
281
  "cardOnFile": "{brand}, अंतिम अंक {last4}",
267
282
  "noPaymentMethod": "कोई सहेजा हुआ तरीका नहीं",
268
283
  "pause": "रोकें",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "Addebito ogni {count} × {interval}",
264
264
  "gateway": "Fornitore di pagamento",
265
265
  "paymentMethod": "Metodo di pagamento",
266
+ "discount": "Sconto",
267
+ "discountPercent": "{percent}%",
268
+ "discountCap": ", fino a {amount} per pagamento",
269
+ "discountEvery": "{code} · {off} di sconto su ogni pagamento{cap}",
270
+ "discountLeft": "{code} · {off} di sconto{cap} · ancora {count} pagamenti, poi {price} {period}",
271
+ "discountLeftOne": "{code} · {off} di sconto{cap} · ancora 1 pagamento, poi {price} {period}",
272
+ "periodDayOne": "al giorno",
273
+ "periodDayOther": "ogni {count} giorni",
274
+ "periodWeekOne": "a settimana",
275
+ "periodWeekOther": "ogni {count} settimane",
276
+ "periodMonthOne": "al mese",
277
+ "periodMonthOther": "ogni {count} mesi",
278
+ "periodYearOne": "all’anno",
279
+ "periodYearOther": "ogni {count} anni",
280
+ "periodOther": "ogni {count} {interval}",
266
281
  "cardOnFile": "{brand} che termina con {last4}",
267
282
  "noPaymentMethod": "Nessuno salvato",
268
283
  "pause": "Metti in pausa",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "Apmokestinama kas {count} × {interval}",
264
264
  "gateway": "Mokėjimų teikėjas",
265
265
  "paymentMethod": "Mokėjimo būdas",
266
+ "discount": "Nuolaida",
267
+ "discountPercent": "{percent} %",
268
+ "discountCap": ", ne daugiau kaip {amount} už mokėjimą",
269
+ "discountEvery": "{code} · {off} nuolaida kiekvienam mokėjimui{cap}",
270
+ "discountLeft": "{code} · {off} nuolaida{cap} · liko mokėjimų: {count}, vėliau {price} {period}",
271
+ "discountLeftOne": "{code} · {off} nuolaida{cap} · liko 1 mokėjimas, vėliau {price} {period}",
272
+ "periodDayOne": "per dieną",
273
+ "periodDayOther": "kas {count} d.",
274
+ "periodWeekOne": "per savaitę",
275
+ "periodWeekOther": "kas {count} sav.",
276
+ "periodMonthOne": "per mėnesį",
277
+ "periodMonthOther": "kas {count} mėn.",
278
+ "periodYearOne": "per metus",
279
+ "periodYearOther": "kas {count} m.",
280
+ "periodOther": "kas {count} {interval}",
266
281
  "cardOnFile": "{brand}, paskutiniai skaitmenys {last4}",
267
282
  "noPaymentMethod": "Nėra išsaugoto",
268
283
  "pause": "Pristabdyti",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "Elke {count} × {interval} gefactureerd",
264
264
  "gateway": "Betaalprovider",
265
265
  "paymentMethod": "Betaalmethode",
266
+ "discount": "Korting",
267
+ "discountPercent": "{percent}%",
268
+ "discountCap": ", maximaal {amount} per betaling",
269
+ "discountEvery": "{code} · {off} korting op elke betaling{cap}",
270
+ "discountLeft": "{code} · {off} korting{cap} · nog {count} betalingen, daarna {price} {period}",
271
+ "discountLeftOne": "{code} · {off} korting{cap} · nog 1 betaling, daarna {price} {period}",
272
+ "periodDayOne": "per dag",
273
+ "periodDayOther": "elke {count} dagen",
274
+ "periodWeekOne": "per week",
275
+ "periodWeekOther": "elke {count} weken",
276
+ "periodMonthOne": "per maand",
277
+ "periodMonthOther": "elke {count} maanden",
278
+ "periodYearOne": "per jaar",
279
+ "periodYearOther": "elke {count} jaar",
280
+ "periodOther": "elke {count} {interval}",
266
281
  "cardOnFile": "{brand} eindigend op {last4}",
267
282
  "noPaymentMethod": "Niets vastgelegd",
268
283
  "pause": "Pauzeren",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "Płatność co {count} × {interval}",
264
264
  "gateway": "Dostawca płatności",
265
265
  "paymentMethod": "Metoda płatności",
266
+ "discount": "Rabat",
267
+ "discountPercent": "{percent}%",
268
+ "discountCap": ", maksymalnie {amount} za płatność",
269
+ "discountEvery": "{code} · {off} rabatu na każdą płatność{cap}",
270
+ "discountLeft": "{code} · {off} rabatu{cap} · pozostałe płatności: {count}, potem {price} {period}",
271
+ "discountLeftOne": "{code} · {off} rabatu{cap} · jeszcze 1 płatność, potem {price} {period}",
272
+ "periodDayOne": "dziennie",
273
+ "periodDayOther": "co {count} dni",
274
+ "periodWeekOne": "tygodniowo",
275
+ "periodWeekOther": "co {count} tygodni",
276
+ "periodMonthOne": "miesięcznie",
277
+ "periodMonthOther": "co {count} miesięcy",
278
+ "periodYearOne": "rocznie",
279
+ "periodYearOther": "co {count} lat",
280
+ "periodOther": "co {count} {interval}",
266
281
  "cardOnFile": "{brand} kończąca się na {last4}",
267
282
  "noPaymentMethod": "Brak zapisanej",
268
283
  "pause": "Wstrzymaj",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "Cobrança a cada {count} × {interval}",
264
264
  "gateway": "Fornecedor de pagamento",
265
265
  "paymentMethod": "Método de pagamento",
266
+ "discount": "Desconto",
267
+ "discountPercent": "{percent}%",
268
+ "discountCap": ", até {amount} por pagamento",
269
+ "discountEvery": "{code} · {off} de desconto em cada pagamento{cap}",
270
+ "discountLeft": "{code} · {off} de desconto{cap} · mais {count} pagamentos, depois {price} {period}",
271
+ "discountLeftOne": "{code} · {off} de desconto{cap} · mais 1 pagamento, depois {price} {period}",
272
+ "periodDayOne": "por dia",
273
+ "periodDayOther": "a cada {count} dias",
274
+ "periodWeekOne": "por semana",
275
+ "periodWeekOther": "a cada {count} semanas",
276
+ "periodMonthOne": "por mês",
277
+ "periodMonthOther": "a cada {count} meses",
278
+ "periodYearOne": "por ano",
279
+ "periodYearOther": "a cada {count} anos",
280
+ "periodOther": "a cada {count} {interval}",
266
281
  "cardOnFile": "{brand} terminado em {last4}",
267
282
  "noPaymentMethod": "Nenhum guardado",
268
283
  "pause": "Pausar",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "Списание раз в {count} × {interval}",
264
264
  "gateway": "Платёжный провайдер",
265
265
  "paymentMethod": "Способ оплаты",
266
+ "discount": "Скидка",
267
+ "discountPercent": "{percent}%",
268
+ "discountCap": ", не более {amount} за платёж",
269
+ "discountEvery": "{code} · скидка {off} на каждый платёж{cap}",
270
+ "discountLeft": "{code} · скидка {off}{cap} · осталось платежей: {count}, затем {price} {period}",
271
+ "discountLeftOne": "{code} · скидка {off}{cap} · остался 1 платёж, затем {price} {period}",
272
+ "periodDayOne": "в день",
273
+ "periodDayOther": "раз в {count} дн.",
274
+ "periodWeekOne": "в неделю",
275
+ "periodWeekOther": "раз в {count} нед.",
276
+ "periodMonthOne": "в месяц",
277
+ "periodMonthOther": "раз в {count} мес.",
278
+ "periodYearOne": "в год",
279
+ "periodYearOther": "раз в {count} г.",
280
+ "periodOther": "раз в {count} {interval}",
266
281
  "cardOnFile": "{brand}, последние цифры {last4}",
267
282
  "noPaymentMethod": "Не сохранён",
268
283
  "pause": "Приостановить",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "Her {count} × {interval} faturalanır",
264
264
  "gateway": "Ödeme sağlayıcısı",
265
265
  "paymentMethod": "Ödeme yöntemi",
266
+ "discount": "İndirim",
267
+ "discountPercent": "%{percent}",
268
+ "discountCap": ", ödeme başına en fazla {amount}",
269
+ "discountEvery": "{code} · her ödemede {off} indirim{cap}",
270
+ "discountLeft": "{code} · {off} indirim{cap} · {count} ödeme daha, sonra {period} {price}",
271
+ "discountLeftOne": "{code} · {off} indirim{cap} · 1 ödeme daha, sonra {period} {price}",
272
+ "periodDayOne": "günlük",
273
+ "periodDayOther": "{count} günde bir",
274
+ "periodWeekOne": "haftalık",
275
+ "periodWeekOther": "{count} haftada bir",
276
+ "periodMonthOne": "aylık",
277
+ "periodMonthOther": "{count} ayda bir",
278
+ "periodYearOne": "yıllık",
279
+ "periodYearOther": "{count} yılda bir",
280
+ "periodOther": "{count} {interval} bir",
266
281
  "cardOnFile": "{last4} ile biten {brand}",
267
282
  "noPaymentMethod": "Kayıtlı yöntem yok",
268
283
  "pause": "Duraklat",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "Оплата кожні {count} × {interval}",
264
264
  "gateway": "Платіжний провайдер",
265
265
  "paymentMethod": "Спосіб оплати",
266
+ "discount": "Знижка",
267
+ "discountPercent": "{percent}%",
268
+ "discountCap": ", не більше {amount} за платіж",
269
+ "discountEvery": "{code} · знижка {off} на кожен платіж{cap}",
270
+ "discountLeft": "{code} · знижка {off}{cap} · залишилося платежів: {count}, потім {price} {period}",
271
+ "discountLeftOne": "{code} · знижка {off}{cap} · залишився 1 платіж, потім {price} {period}",
272
+ "periodDayOne": "на день",
273
+ "periodDayOther": "раз на {count} дн.",
274
+ "periodWeekOne": "на тиждень",
275
+ "periodWeekOther": "раз на {count} тиж.",
276
+ "periodMonthOne": "на місяць",
277
+ "periodMonthOther": "раз на {count} міс.",
278
+ "periodYearOne": "на рік",
279
+ "periodYearOther": "раз на {count} р.",
280
+ "periodOther": "раз на {count} {interval}",
266
281
  "cardOnFile": "{brand}, що закінчується на {last4}",
267
282
  "noPaymentMethod": "Не збережено",
268
283
  "pause": "Призупинити",
@@ -263,6 +263,21 @@
263
263
  "billingOther": "每 {count} × {interval} 计费",
264
264
  "gateway": "支付服务商",
265
265
  "paymentMethod": "支付方式",
266
+ "discount": "折扣",
267
+ "discountPercent": "{percent}%",
268
+ "discountCap": ",每次最多 {amount}",
269
+ "discountEvery": "{code} · 每次付款优惠 {off}{cap}",
270
+ "discountLeft": "{code} · 优惠 {off}{cap} · 还剩 {count} 次付款,之后 {period} {price}",
271
+ "discountLeftOne": "{code} · 优惠 {off}{cap} · 还剩 1 次付款,之后 {period} {price}",
272
+ "periodDayOne": "每天",
273
+ "periodDayOther": "每 {count} 天",
274
+ "periodWeekOne": "每周",
275
+ "periodWeekOther": "每 {count} 周",
276
+ "periodMonthOne": "每月",
277
+ "periodMonthOther": "每 {count} 个月",
278
+ "periodYearOne": "每年",
279
+ "periodYearOther": "每 {count} 年",
280
+ "periodOther": "每 {count} {interval}",
266
281
  "cardOnFile": "{brand} 尾号 {last4}",
267
282
  "noPaymentMethod": "未保存支付方式",
268
283
  "pause": "暂停",
@@ -47,6 +47,7 @@ import {
47
47
  subscriptionTiming,
48
48
  subscriptionTimingKey,
49
49
  } from './account-subscriptions';
50
+ import { gatewayLabel } from './account-payment-method';
50
51
  import { callCustomerApi, safeExternalHref } from './customer-account';
51
52
  import { hostedPageHref } from './hosted-page';
52
53
  import type {
@@ -148,7 +149,7 @@ export function AccountSubscriptions() {
148
149
  dash in its place would read as "free". */}
149
150
  {subscription.plan ? (
150
151
  <span className="sxa:shrink-0 sxa:text-small sxa:font-semibold sxa:tabular-nums">
151
- {formatAccountAmount(subscription.plan.price, subscription.plan.currency)}
152
+ {nextPaymentAmount(subscription)}
152
153
  </span>
153
154
  ) : null}
154
155
  <SubscriptionStatus subscription={subscription} />
@@ -215,6 +216,7 @@ function SubscriptionDetail({
215
216
  };
216
217
 
217
218
  const billing = subscription.plan ? billingMessage(subscription.plan) : null;
219
+ const discount = discountLine(subscription);
218
220
 
219
221
  return (
220
222
  <div>
@@ -246,7 +248,7 @@ function SubscriptionDetail({
246
248
  {subscription.plan ? (
247
249
  <>
248
250
  <p className="sxa:text-h2 sxa:font-bold sxa:tabular-nums">
249
- {formatAccountAmount(subscription.plan.price, subscription.plan.currency)}
251
+ {nextPaymentAmount(subscription)}
250
252
  </p>
251
253
  {billing ? (
252
254
  <p className="sxa:mt-1 sxa:text-small sxa:text-muted">{t(billing.key, billing.params)}</p>
@@ -257,7 +259,7 @@ function SubscriptionDetail({
257
259
  <p className="sxa:mt-2 sxa:text-small sxa:text-muted">{timingLabel(subscription)}</p>
258
260
 
259
261
  <dl className="sxa:mt-5 sxa:space-y-2.5 sxa:border-t sxa:pt-4 sxa:text-small">
260
- <DetailRow label={t('subscriptions.gateway')}>{subscription.gateway}</DetailRow>
262
+ <DetailRow label={t('subscriptions.gateway')}>{gatewayLabel(subscription.gateway)}</DetailRow>
261
263
  <DetailRow label={t('subscriptions.paymentMethod')}>
262
264
  {/* Brand AND last four, or the producer's own word for the method.
263
265
  Half a card ("Visa ending —") is not a payment method. */}
@@ -268,6 +270,13 @@ function SubscriptionDetail({
268
270
  })
269
271
  : subscription.payment_method?.type ?? t('subscriptions.noPaymentMethod')}
270
272
  </DetailRow>
273
+ {discount ? (
274
+ // Wraps instead of truncating: the line ends with the price after the term.
275
+ <div className="sxa:flex sxa:items-start sxa:justify-between sxa:gap-4">
276
+ <dt className="sxa:shrink-0 sxa:text-muted">{t('subscriptions.discount')}</dt>
277
+ <dd className="sxa:min-w-0 sxa:text-right sxa:font-medium">{discount}</dd>
278
+ </div>
279
+ ) : null}
271
280
  </dl>
272
281
  </AccountCard>
273
282
 
@@ -672,6 +681,41 @@ function renewalCopy(subscription: SdkCustomerSubscription, state: OpenRenewalSt
672
681
  return { note, payLabel: t('subscriptions.payAmount', { amount }) };
673
682
  }
674
683
 
684
+ /**
685
+ * The plan's coupon term as the buyer reads it (Design Batch 4, feature 1), the portal's line:
686
+ * "LAUNCH20 · 20 % off · 2 more payments, then $9.99 a month", or "TENOFF · 10 % off every
687
+ * payment", with ", up to $3.00 each" when the coupon's cap bounds each payment. Every amount is
688
+ * the producer's `discount`. Null without a running term.
689
+ */
690
+ export function discountLine(subscription: SdkCustomerSubscription): string | null {
691
+ const { discount, plan } = subscription;
692
+ // The producer reads a term only for the plan it serves.
693
+ if (!discount || !plan) return null;
694
+ const off = discount.discount_type === 'PERCENTAGE'
695
+ ? t('subscriptions.discountPercent', { percent: Number(discount.discount_value) })
696
+ : formatAccountAmount(discount.discount_amount, discount.currency);
697
+ const cap = discount.max_discount === null
698
+ ? ''
699
+ : t('subscriptions.discountCap', { amount: formatAccountAmount(discount.max_discount, discount.currency) });
700
+ if (discount.payments_total === null) return t('subscriptions.discountEvery', { code: discount.code, off, cap });
701
+ const left = discount.payments_total - discount.payments_used;
702
+ const period = billingMessage(plan, 'period');
703
+ return t(left === 1 ? 'subscriptions.discountLeftOne' : 'subscriptions.discountLeft', {
704
+ code: discount.code,
705
+ off,
706
+ cap,
707
+ count: left,
708
+ price: formatAccountAmount(discount.full_amount, discount.currency),
709
+ period: t(period.key, period.params),
710
+ });
711
+ }
712
+
713
+ /** What the next payment bills: the term's `next_amount` while it runs, else the plan price. */
714
+ export function nextPaymentAmount(subscription: SdkCustomerSubscription): string | null {
715
+ if (subscription.discount) return formatAccountAmount(subscription.discount.next_amount, subscription.discount.currency);
716
+ return subscription.plan ? formatAccountAmount(subscription.plan.price, subscription.plan.currency) : null;
717
+ }
718
+
675
719
  function DetailRow({ label, children }: { label: string; children: ReactNode }) {
676
720
  return (
677
721
  <div className="sxa:flex sxa:items-center sxa:justify-between sxa:gap-4">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shoppexio/storefront-react",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "React hooks, models and a customer account for Shoppex code storefronts",
5
5
  "type": "module",
6
6
  "homepage": "https://docs.shoppex.io/storefront/code-storefront-development",