@shoppexio/storefront-react 0.5.0 → 0.6.1

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.
@@ -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": "Призупинити",
@@ -350,7 +365,8 @@
350
365
  "historyFirstOrder": "Перше замовлення",
351
366
  "historyRenewal": "Продовження",
352
367
  "historyPlanChange": "Зміна тарифу: {from} на {to}",
353
- "historyDays": "за {days} з {total} дн."
368
+ "historyDays": "за {days} з {total} дн.",
369
+ "historyCouponPayment": "{code} · {payment} з {total}"
354
370
  },
355
371
  "favorites": {
356
372
  "loading": "Завантажуємо ваше обране",
@@ -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": "暂停",
@@ -350,7 +365,8 @@
350
365
  "historyFirstOrder": "首次订单",
351
366
  "historyRenewal": "续订",
352
367
  "historyPlanChange": "套餐变更:{from} 改为 {to}",
353
- "historyDays": "{total} 天中的 {days} 天"
368
+ "historyDays": "{total} 天中的 {days} 天",
369
+ "historyCouponPayment": "{code} · 第 {payment}/{total} 次"
354
370
  },
355
371
  "favorites": {
356
372
  "loading": "正在加载你的收藏",
@@ -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">
@@ -732,6 +776,7 @@ export function BillingHistoryList({ payload, reload }: { payload: unknown; relo
732
776
  {history.invoices.map((invoice) => {
733
777
  const title = billingEntryTitle(invoice);
734
778
  const days = billingEntryDays(invoice);
779
+ const coupon = invoice.coupon_payment;
735
780
  return (
736
781
  <li key={invoice.uniqid}>
737
782
  <Link
@@ -744,6 +789,14 @@ export function BillingHistoryList({ payload, reload }: { payload: unknown; relo
744
789
  {formatAccountDate(invoice.created_at)}
745
790
  {days ? ` · ${t(days.key, days.params)}` : null}
746
791
  </p>
792
+ {/* A discounted payment names its code and which term payment it was, as the dashboard does: "LAUNCH20 · 2 of 3", the code alone when every payment is discounted. Its own line, so it does not wrap after the date at 375 px. */}
793
+ {coupon ? (
794
+ <p className="sxa:mt-0.5 sxa:text-micro sxa:tabular-nums sxa:text-muted">
795
+ {coupon.payments_total === null
796
+ ? coupon.code
797
+ : t('subscriptions.historyCouponPayment', { code: coupon.code, payment: coupon.payment, total: coupon.payments_total })}
798
+ </p>
799
+ ) : null}
747
800
  </div>
748
801
  <div className="sxa:flex sxa:shrink-0 sxa:flex-col sxa:items-end sxa:gap-1.5 sxa:sm:flex-row sxa:sm:items-center sxa:sm:gap-3">
749
802
  <span className="sxa:text-small sxa:font-semibold sxa:tabular-nums">{formatAccountAmount(invoice.total, invoice.currency)}</span>
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.1",
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",