@blocklet/payment-react 1.26.3 → 1.26.5

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.
Files changed (33) hide show
  1. package/es/checkout-v2/components/dialogs/checkout-dialogs.js +2 -1
  2. package/es/checkout-v2/components/right/customer-info-card.js +5 -0
  3. package/es/checkout-v2/utils/format.d.ts +1 -1
  4. package/es/checkout-v2/utils/format.js +3 -2
  5. package/es/components/service-suspended-dialog.js +8 -6
  6. package/es/libs/util.js +9 -20
  7. package/es/locales/en.js +4 -2
  8. package/es/locales/zh.js +2 -0
  9. package/es/payment/form/index.js +1 -10
  10. package/es/payment/form/stripe/form.d.ts +3 -1
  11. package/es/payment/form/stripe/form.js +32 -15
  12. package/lib/checkout-v2/components/dialogs/checkout-dialogs.js +2 -1
  13. package/lib/checkout-v2/components/right/customer-info-card.js +5 -0
  14. package/lib/checkout-v2/utils/format.d.ts +1 -1
  15. package/lib/checkout-v2/utils/format.js +6 -2
  16. package/lib/components/service-suspended-dialog.js +8 -6
  17. package/lib/libs/util.js +11 -7
  18. package/lib/locales/en.js +4 -2
  19. package/lib/locales/zh.js +2 -0
  20. package/lib/payment/form/index.js +4 -9
  21. package/lib/payment/form/stripe/form.d.ts +3 -1
  22. package/lib/payment/form/stripe/form.js +37 -17
  23. package/package.json +4 -4
  24. package/src/checkout-v2/components/dialogs/checkout-dialogs.tsx +6 -0
  25. package/src/checkout-v2/components/right/customer-info-card.tsx +7 -0
  26. package/src/checkout-v2/layouts/checkout-layout.tsx +13 -12
  27. package/src/checkout-v2/utils/format.ts +8 -3
  28. package/src/components/service-suspended-dialog.tsx +8 -6
  29. package/src/libs/util.ts +9 -20
  30. package/src/locales/en.tsx +4 -2
  31. package/src/locales/zh.tsx +2 -0
  32. package/src/payment/form/index.tsx +1 -9
  33. package/src/payment/form/stripe/form.tsx +29 -11
@@ -50,7 +50,8 @@ export default function CheckoutDialogs() {
50
50
  },
51
51
  returnUrl: getRedirectUrl(session),
52
52
  onConfirm: submit.stripeConfirm,
53
- onCancel: submit.stripeCancel
53
+ onCancel: submit.stripeCancel,
54
+ onSkip: stripeContext.noPaymentRequired && `${session?.metadata?.allow_skip_payment_method}` === "true" ? submit.stripeSkip : void 0
54
55
  }
55
56
  ),
56
57
  submit.status === "confirming_price" && submit.context?.type === "price_change" && /* @__PURE__ */ jsx(
@@ -36,6 +36,11 @@ export default function CustomerInfoCard({ form, isLoggedIn }) {
36
36
  [form.onChange]
37
37
  // eslint-disable-line react-hooks/exhaustive-deps
38
38
  );
39
+ useEffect(() => {
40
+ if (ready && !showEditForm && Object.keys(form.errors).length > 0) {
41
+ setShowEditForm(true);
42
+ }
43
+ }, [form.errors]);
39
44
  if (!isLoggedIn || !ready) return null;
40
45
  if (!showEditForm) {
41
46
  return /* @__PURE__ */ jsxs(Box, { sx: { mt: 2 }, children: [
@@ -43,7 +43,7 @@ export declare const whiteTooltipSx: {
43
43
  };
44
44
  };
45
45
  };
46
- export declare function formatTrialText(t: (key: string) => string, days: number, interval: string): string;
46
+ export declare function formatTrialText(t: (key: string, params?: Record<string, any>) => string, days: number, interval: string): string;
47
47
  type TFn = (key: string, params?: Record<string, any>) => string;
48
48
  export type ItemTypeBadge = 'subscription' | 'topup' | 'oneTime';
49
49
  interface ItemMeta {
@@ -86,8 +86,9 @@ export const whiteTooltipSx = {
86
86
  }
87
87
  };
88
88
  export function formatTrialText(t, days, interval) {
89
- const intervalLabel = t(`common.${interval || "day"}`);
90
- return `${days} ${intervalLabel}${days > 1 ? "s" : ""} free`;
89
+ const key = interval || "day";
90
+ const intervalKey = days > 1 ? `common.${key}s` : `common.${key}`;
91
+ return t("payment.checkout.free", { count: days, interval: t(intervalKey) });
91
92
  }
92
93
  export function getSessionHeaderMeta(t, session, product, items) {
93
94
  const mode = session?.mode || "payment";
@@ -10,12 +10,14 @@ export default function ServiceSuspendedDialog({ open, onClose }) {
10
10
  {
11
11
  open,
12
12
  onClose,
13
- PaperProps: {
14
- sx: {
15
- borderRadius: 3,
16
- maxWidth: 400,
17
- mx: "auto",
18
- overflow: "hidden"
13
+ slotProps: {
14
+ paper: {
15
+ sx: {
16
+ borderRadius: 3,
17
+ maxWidth: 400,
18
+ mx: "auto",
19
+ overflow: "hidden"
20
+ }
19
21
  }
20
22
  },
21
23
  children: /* @__PURE__ */ jsxs(DialogContent, { sx: { p: 0 }, children: [
package/es/libs/util.js CHANGED
@@ -734,34 +734,23 @@ export function hasMultipleRecurringIntervals(items) {
734
734
  }
735
735
  export function getFreeTrialTime({ trialInDays, trialEnd }, locale = "en") {
736
736
  const now = dayjs().unix();
737
+ const plural = (singular, count) => t(`common.${count > 1 ? `${singular}s` : singular}`, locale);
737
738
  if (trialEnd > 0 && trialEnd > now) {
738
739
  if (trialEnd - now < 3600) {
739
- return {
740
- count: Math.ceil((trialEnd - now) / 60),
741
- interval: t("common.minute", locale)
742
- };
740
+ const count2 = Math.ceil((trialEnd - now) / 60);
741
+ return { count: count2, interval: plural("minute", count2) };
743
742
  }
744
743
  if (trialEnd - now < 86400) {
745
- return {
746
- count: Math.ceil((trialEnd - now) / 3600),
747
- interval: t("common.hour", locale)
748
- };
744
+ const count2 = Math.ceil((trialEnd - now) / 3600);
745
+ return { count: count2, interval: plural("hour", count2) };
749
746
  }
750
- return {
751
- count: Math.ceil((trialEnd - now) / 86400),
752
- interval: t("common.day", locale)
753
- };
747
+ const count = Math.ceil((trialEnd - now) / 86400);
748
+ return { count, interval: plural("day", count) };
754
749
  }
755
750
  if (trialInDays > 0) {
756
- return {
757
- count: trialInDays,
758
- interval: t("common.day", locale)
759
- };
751
+ return { count: trialInDays, interval: plural("day", trialInDays) };
760
752
  }
761
- return {
762
- count: 0,
763
- interval: t("common.day", locale)
764
- };
753
+ return { count: 0, interval: t("common.day", locale) };
765
754
  }
766
755
  export function formatCheckoutHeadlines(items, currency, { trialInDays, trialEnd }, locale = "en", { exchangeRate = null } = {}) {
767
756
  const brand = getStatementDescriptor(items);
package/es/locales/en.js CHANGED
@@ -98,7 +98,7 @@ export default flat({
98
98
  continue: "Continue",
99
99
  qty: "Qty {count}",
100
100
  each: "{unit} each",
101
- trial: "Free for {count} {interval}{count > 1 ? 's' : ''}",
101
+ trial: "Free for {count} {interval}",
102
102
  billed: "billed {rule}",
103
103
  metered: "based on usage",
104
104
  minute: "minute",
@@ -115,6 +115,7 @@ export default flat({
115
115
  month3: "every 3 months",
116
116
  month6: "every 6 months",
117
117
  recurring: "every {count} {interval}",
118
+ minutes: "minutes",
118
119
  hours: "hours",
119
120
  days: "days",
120
121
  weeks: "weeks",
@@ -265,6 +266,7 @@ export default flat({
265
266
  configTip: "Configure donation settings in Payment Kit"
266
267
  },
267
268
  cardPay: "{action} with bank card",
269
+ skipPaymentMethod: "Skip, bind later",
268
270
  empty: "Nothing to pay",
269
271
  per: "per",
270
272
  pay: "Pay {payee}",
@@ -275,7 +277,7 @@ export default flat({
275
277
  then: "Then {subscription} {recurring}",
276
278
  meteredThen: "Then {recurring} based on usage",
277
279
  metered: "{recurring} based on usage",
278
- free: "{count} {interval}{count > 1 ? 's' : ''} free",
280
+ free: "{count} {interval} free",
279
281
  least: "continue with at least",
280
282
  completed: {
281
283
  payment: "Purchase successful",
package/es/locales/zh.js CHANGED
@@ -115,6 +115,7 @@ export default flat({
115
115
  month3: "\u6BCF\u5B63\u5EA6",
116
116
  month6: "\u6BCF\u534A\u5E74",
117
117
  recurring: "\u6BCF{count}{interval}",
118
+ minutes: "\u5206\u949F",
118
119
  hours: "\u5C0F\u65F6",
119
120
  days: "\u5929",
120
121
  weeks: "\u5468",
@@ -265,6 +266,7 @@ export default flat({
265
266
  configTip: "\u524D\u5F80 Payment Kit \u914D\u7F6E\u6253\u8D4F\u9009\u9879"
266
267
  },
267
268
  cardPay: "\u4F7F\u7528\u94F6\u884C\u5361{action}",
269
+ skipPaymentMethod: "\u8DF3\u8FC7\uFF0C\u7A0D\u540E\u7ED1\u5B9A",
268
270
  empty: "\u6CA1\u6709\u53EF\u652F\u4ED8\u7684\u9879\u76EE",
269
271
  per: "\u6BCF",
270
272
  pay: "\u4ED8\u6B3E\u7ED9 {payee}",
@@ -1451,16 +1451,7 @@ export default function PaymentForm({
1451
1451
  }
1452
1452
  }
1453
1453
  ),
1454
- state.serviceSuspended && /* @__PURE__ */ jsx(
1455
- ConfirmDialog,
1456
- {
1457
- onConfirm: () => setState({ serviceSuspended: false }),
1458
- onCancel: () => setState({ serviceSuspended: false }),
1459
- title: t("payment.checkout.stopAcceptingOrders.title"),
1460
- message: t("payment.checkout.stopAcceptingOrders.description"),
1461
- confirm: t("common.confirm")
1462
- }
1463
- ),
1454
+ state.serviceSuspended && /* @__PURE__ */ jsx(ServiceSuspendedDialog, { open: true, onClose: () => setState({ serviceSuspended: false }) }),
1464
1455
  FastCheckoutConfirmDialog,
1465
1456
  CreditInsufficientDialog,
1466
1457
  PriceUpdatedDialog,
@@ -5,6 +5,7 @@ export type StripeCheckoutFormProps = {
5
5
  customer: TCustomer;
6
6
  mode: string;
7
7
  onConfirm: Function;
8
+ onSkip?: Function | null;
8
9
  returnUrl?: string;
9
10
  submitButtonText?: string;
10
11
  };
@@ -16,8 +17,9 @@ export type StripeCheckoutProps = {
16
17
  customer: TCustomer;
17
18
  onConfirm: Function;
18
19
  onCancel: Function;
20
+ onSkip?: Function | null;
19
21
  returnUrl?: string;
20
22
  title?: string;
21
23
  submitButtonText?: string;
22
24
  };
23
- export default function StripeCheckout({ clientSecret, intentType, publicKey, mode, customer, onConfirm, onCancel, returnUrl, title, submitButtonText, }: StripeCheckoutProps): import("react").JSX.Element;
25
+ export default function StripeCheckout({ clientSecret, intentType, publicKey, mode, customer, onConfirm, onCancel, onSkip, returnUrl, title, submitButtonText, }: StripeCheckoutProps): import("react").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import Center from "@arcblock/ux/lib/Center";
3
3
  import Dialog from "@arcblock/ux/lib/Dialog";
4
4
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
@@ -23,6 +23,7 @@ function StripeCheckoutForm({
23
23
  customer,
24
24
  mode,
25
25
  onConfirm,
26
+ onSkip = null,
26
27
  returnUrl = "",
27
28
  submitButtonText = ""
28
29
  }) {
@@ -191,20 +192,34 @@ function StripeCheckoutForm({
191
192
  }
192
193
  ) }),
193
194
  (!stripe || !elements || !state.loaded) && /* @__PURE__ */ jsx(Center, { relative: "parent", children: /* @__PURE__ */ jsx(CircularProgress, {}) }),
194
- stripe && elements && state.loaded && /* @__PURE__ */ jsx(
195
- LoadingButton,
196
- {
197
- fullWidth: true,
198
- sx: { mt: 2, mb: 1, borderRadius: 0, fontSize: "0.875rem" },
199
- type: "submit",
200
- disabled: state.confirming || !state.loaded,
201
- loading: state.confirming,
202
- variant: "contained",
203
- color: "primary",
204
- size: "large",
205
- children: submitButtonText || t("payment.checkout.continue", { action: t(`payment.checkout.${mode}`) })
206
- }
207
- ),
195
+ stripe && elements && state.loaded && /* @__PURE__ */ jsxs(Fragment, { children: [
196
+ /* @__PURE__ */ jsx(
197
+ LoadingButton,
198
+ {
199
+ fullWidth: true,
200
+ sx: { mt: 2, mb: 1, borderRadius: 0, fontSize: "0.875rem" },
201
+ type: "submit",
202
+ disabled: state.confirming || !state.loaded,
203
+ loading: state.confirming,
204
+ variant: "contained",
205
+ color: "primary",
206
+ size: "large",
207
+ children: submitButtonText || t("payment.checkout.continue", { action: t(`payment.checkout.${mode}`) })
208
+ }
209
+ ),
210
+ onSkip && /* @__PURE__ */ jsx(
211
+ LoadingButton,
212
+ {
213
+ fullWidth: true,
214
+ sx: { mb: 1, borderRadius: 0, fontSize: "0.875rem", borderColor: "divider" },
215
+ disabled: state.confirming,
216
+ variant: "outlined",
217
+ color: "primary",
218
+ onClick: () => onSkip(),
219
+ children: t("payment.checkout.skipPaymentMethod", { defaultValue: "Skip, bind later" })
220
+ }
221
+ )
222
+ ] }),
208
223
  state.message && /* @__PURE__ */ jsx(Typography, { sx: { mt: 1, color: "error.main" }, children: state.message })
209
224
  ] });
210
225
  }
@@ -225,6 +240,7 @@ export default function StripeCheckout({
225
240
  customer,
226
241
  onConfirm,
227
242
  onCancel,
243
+ onSkip = null,
228
244
  returnUrl = "",
229
245
  title = "",
230
246
  submitButtonText = ""
@@ -300,6 +316,7 @@ export default function StripeCheckout({
300
316
  mode,
301
317
  customer,
302
318
  onConfirm,
319
+ onSkip,
303
320
  returnUrl,
304
321
  submitButtonText
305
322
  }
@@ -56,7 +56,8 @@ function CheckoutDialogs() {
56
56
  },
57
57
  returnUrl: getRedirectUrl(session),
58
58
  onConfirm: submit.stripeConfirm,
59
- onCancel: submit.stripeCancel
59
+ onCancel: submit.stripeCancel,
60
+ onSkip: stripeContext.noPaymentRequired && `${session?.metadata?.allow_skip_payment_method}` === "true" ? submit.stripeSkip : void 0
60
61
  }), submit.status === "confirming_price" && submit.context?.type === "price_change" && /* @__PURE__ */(0, _jsxRuntime.jsx)(_priceChangeConfirm.default, {
61
62
  open: true,
62
63
  changePercent: submit.context.changePercent,
@@ -46,6 +46,11 @@ function CustomerInfoCard({
46
46
  const handleChange = (0, _react.useCallback)((field, value) => form.onChange(field, value), [form.onChange]
47
47
  // eslint-disable-line react-hooks/exhaustive-deps
48
48
  );
49
+ (0, _react.useEffect)(() => {
50
+ if (ready && !showEditForm && Object.keys(form.errors).length > 0) {
51
+ setShowEditForm(true);
52
+ }
53
+ }, [form.errors]);
49
54
  if (!isLoggedIn || !ready) return null;
50
55
  if (!showEditForm) {
51
56
  return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
@@ -43,7 +43,7 @@ export declare const whiteTooltipSx: {
43
43
  };
44
44
  };
45
45
  };
46
- export declare function formatTrialText(t: (key: string) => string, days: number, interval: string): string;
46
+ export declare function formatTrialText(t: (key: string, params?: Record<string, any>) => string, days: number, interval: string): string;
47
47
  type TFn = (key: string, params?: Record<string, any>) => string;
48
48
  export type ItemTypeBadge = 'subscription' | 'topup' | 'oneTime';
49
49
  interface ItemMeta {
@@ -116,8 +116,12 @@ const whiteTooltipSx = exports.whiteTooltipSx = {
116
116
  }
117
117
  };
118
118
  function formatTrialText(t, days, interval) {
119
- const intervalLabel = t(`common.${interval || "day"}`);
120
- return `${days} ${intervalLabel}${days > 1 ? "s" : ""} free`;
119
+ const key = interval || "day";
120
+ const intervalKey = days > 1 ? `common.${key}s` : `common.${key}`;
121
+ return t("payment.checkout.free", {
122
+ count: days,
123
+ interval: t(intervalKey)
124
+ });
121
125
  }
122
126
  function getSessionHeaderMeta(t, session, product, items) {
123
127
  const mode = session?.mode || "payment";
@@ -20,12 +20,14 @@ function ServiceSuspendedDialog({
20
20
  return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Dialog, {
21
21
  open,
22
22
  onClose,
23
- PaperProps: {
24
- sx: {
25
- borderRadius: 3,
26
- maxWidth: 400,
27
- mx: "auto",
28
- overflow: "hidden"
23
+ slotProps: {
24
+ paper: {
25
+ sx: {
26
+ borderRadius: 3,
27
+ maxWidth: 400,
28
+ mx: "auto",
29
+ overflow: "hidden"
30
+ }
29
31
  }
30
32
  },
31
33
  children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.DialogContent, {
package/lib/libs/util.js CHANGED
@@ -891,28 +891,32 @@ function getFreeTrialTime({
891
891
  trialEnd
892
892
  }, locale = "en") {
893
893
  const now = (0, _dayjs.default)().unix();
894
+ const plural = (singular, count) => (0, _locales.t)(`common.${count > 1 ? `${singular}s` : singular}`, locale);
894
895
  if (trialEnd > 0 && trialEnd > now) {
895
896
  if (trialEnd - now < 3600) {
897
+ const count2 = Math.ceil((trialEnd - now) / 60);
896
898
  return {
897
- count: Math.ceil((trialEnd - now) / 60),
898
- interval: (0, _locales.t)("common.minute", locale)
899
+ count: count2,
900
+ interval: plural("minute", count2)
899
901
  };
900
902
  }
901
903
  if (trialEnd - now < 86400) {
904
+ const count2 = Math.ceil((trialEnd - now) / 3600);
902
905
  return {
903
- count: Math.ceil((trialEnd - now) / 3600),
904
- interval: (0, _locales.t)("common.hour", locale)
906
+ count: count2,
907
+ interval: plural("hour", count2)
905
908
  };
906
909
  }
910
+ const count = Math.ceil((trialEnd - now) / 86400);
907
911
  return {
908
- count: Math.ceil((trialEnd - now) / 86400),
909
- interval: (0, _locales.t)("common.day", locale)
912
+ count,
913
+ interval: plural("day", count)
910
914
  };
911
915
  }
912
916
  if (trialInDays > 0) {
913
917
  return {
914
918
  count: trialInDays,
915
- interval: (0, _locales.t)("common.day", locale)
919
+ interval: plural("day", trialInDays)
916
920
  };
917
921
  }
918
922
  return {
package/lib/locales/en.js CHANGED
@@ -105,7 +105,7 @@ module.exports = (0, _flat.default)({
105
105
  continue: "Continue",
106
106
  qty: "Qty {count}",
107
107
  each: "{unit} each",
108
- trial: "Free for {count} {interval}{count > 1 ? 's' : ''}",
108
+ trial: "Free for {count} {interval}",
109
109
  billed: "billed {rule}",
110
110
  metered: "based on usage",
111
111
  minute: "minute",
@@ -122,6 +122,7 @@ module.exports = (0, _flat.default)({
122
122
  month3: "every 3 months",
123
123
  month6: "every 6 months",
124
124
  recurring: "every {count} {interval}",
125
+ minutes: "minutes",
125
126
  hours: "hours",
126
127
  days: "days",
127
128
  weeks: "weeks",
@@ -272,6 +273,7 @@ module.exports = (0, _flat.default)({
272
273
  configTip: "Configure donation settings in Payment Kit"
273
274
  },
274
275
  cardPay: "{action} with bank card",
276
+ skipPaymentMethod: "Skip, bind later",
275
277
  empty: "Nothing to pay",
276
278
  per: "per",
277
279
  pay: "Pay {payee}",
@@ -282,7 +284,7 @@ module.exports = (0, _flat.default)({
282
284
  then: "Then {subscription} {recurring}",
283
285
  meteredThen: "Then {recurring} based on usage",
284
286
  metered: "{recurring} based on usage",
285
- free: "{count} {interval}{count > 1 ? 's' : ''} free",
287
+ free: "{count} {interval} free",
286
288
  least: "continue with at least",
287
289
  completed: {
288
290
  payment: "Purchase successful",
package/lib/locales/zh.js CHANGED
@@ -122,6 +122,7 @@ module.exports = (0, _flat.default)({
122
122
  month3: "\u6BCF\u5B63\u5EA6",
123
123
  month6: "\u6BCF\u534A\u5E74",
124
124
  recurring: "\u6BCF{count}{interval}",
125
+ minutes: "\u5206\u949F",
125
126
  hours: "\u5C0F\u65F6",
126
127
  days: "\u5929",
127
128
  weeks: "\u5468",
@@ -272,6 +273,7 @@ module.exports = (0, _flat.default)({
272
273
  configTip: "\u524D\u5F80 Payment Kit \u914D\u7F6E\u6253\u8D4F\u9009\u9879"
273
274
  },
274
275
  cardPay: "\u4F7F\u7528\u94F6\u884C\u5361{action}",
276
+ skipPaymentMethod: "\u8DF3\u8FC7\uFF0C\u7A0D\u540E\u7ED1\u5B9A",
275
277
  empty: "\u6CA1\u6709\u53EF\u652F\u4ED8\u7684\u9879\u76EE",
276
278
  per: "\u6BCF",
277
279
  pay: "\u4ED8\u6B3E\u7ED9 {payee}",
@@ -1630,16 +1630,11 @@ function PaymentForm({
1630
1630
  }),
1631
1631
  title: t("payment.customer.pastDue.alert.title")
1632
1632
  }
1633
- }), state.serviceSuspended && /* @__PURE__ */(0, _jsxRuntime.jsx)(_confirm.default, {
1634
- onConfirm: () => setState({
1635
- serviceSuspended: false
1636
- }),
1637
- onCancel: () => setState({
1633
+ }), state.serviceSuspended && /* @__PURE__ */(0, _jsxRuntime.jsx)(_serviceSuspendedDialog.default, {
1634
+ open: true,
1635
+ onClose: () => setState({
1638
1636
  serviceSuspended: false
1639
- }),
1640
- title: t("payment.checkout.stopAcceptingOrders.title"),
1641
- message: t("payment.checkout.stopAcceptingOrders.description"),
1642
- confirm: t("common.confirm")
1637
+ })
1643
1638
  }), FastCheckoutConfirmDialog, CreditInsufficientDialog, PriceUpdatedDialog, state.priceChangeConfirm?.open && /* @__PURE__ */(0, _jsxRuntime.jsx)(_priceChangeConfirm.default, {
1644
1639
  open: true,
1645
1640
  changePercent: state.priceChangeConfirm.changePercent,
@@ -5,6 +5,7 @@ export type StripeCheckoutFormProps = {
5
5
  customer: TCustomer;
6
6
  mode: string;
7
7
  onConfirm: Function;
8
+ onSkip?: Function | null;
8
9
  returnUrl?: string;
9
10
  submitButtonText?: string;
10
11
  };
@@ -16,8 +17,9 @@ export type StripeCheckoutProps = {
16
17
  customer: TCustomer;
17
18
  onConfirm: Function;
18
19
  onCancel: Function;
20
+ onSkip?: Function | null;
19
21
  returnUrl?: string;
20
22
  title?: string;
21
23
  submitButtonText?: string;
22
24
  };
23
- export default function StripeCheckout({ clientSecret, intentType, publicKey, mode, customer, onConfirm, onCancel, returnUrl, title, submitButtonText, }: StripeCheckoutProps): import("react").JSX.Element;
25
+ export default function StripeCheckout({ clientSecret, intentType, publicKey, mode, customer, onConfirm, onCancel, onSkip, returnUrl, title, submitButtonText, }: StripeCheckoutProps): import("react").JSX.Element;
@@ -37,6 +37,7 @@ function StripeCheckoutForm({
37
37
  customer,
38
38
  mode,
39
39
  onConfirm,
40
+ onSkip = null,
40
41
  returnUrl = "",
41
42
  submitButtonText = ""
42
43
  }) {
@@ -234,23 +235,40 @@ function StripeCheckoutForm({
234
235
  }), (!stripe || !elements || !state.loaded) && /* @__PURE__ */(0, _jsxRuntime.jsx)(_Center.default, {
235
236
  relative: "parent",
236
237
  children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.CircularProgress, {})
237
- }), stripe && elements && state.loaded && /* @__PURE__ */(0, _jsxRuntime.jsx)(_loadingButton.default, {
238
- fullWidth: true,
239
- sx: {
240
- mt: 2,
241
- mb: 1,
242
- borderRadius: 0,
243
- fontSize: "0.875rem"
244
- },
245
- type: "submit",
246
- disabled: state.confirming || !state.loaded,
247
- loading: state.confirming,
248
- variant: "contained",
249
- color: "primary",
250
- size: "large",
251
- children: submitButtonText || t("payment.checkout.continue", {
252
- action: t(`payment.checkout.${mode}`)
253
- })
238
+ }), stripe && elements && state.loaded && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
239
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_loadingButton.default, {
240
+ fullWidth: true,
241
+ sx: {
242
+ mt: 2,
243
+ mb: 1,
244
+ borderRadius: 0,
245
+ fontSize: "0.875rem"
246
+ },
247
+ type: "submit",
248
+ disabled: state.confirming || !state.loaded,
249
+ loading: state.confirming,
250
+ variant: "contained",
251
+ color: "primary",
252
+ size: "large",
253
+ children: submitButtonText || t("payment.checkout.continue", {
254
+ action: t(`payment.checkout.${mode}`)
255
+ })
256
+ }), onSkip && /* @__PURE__ */(0, _jsxRuntime.jsx)(_loadingButton.default, {
257
+ fullWidth: true,
258
+ sx: {
259
+ mb: 1,
260
+ borderRadius: 0,
261
+ fontSize: "0.875rem",
262
+ borderColor: "divider"
263
+ },
264
+ disabled: state.confirming,
265
+ variant: "outlined",
266
+ color: "primary",
267
+ onClick: () => onSkip(),
268
+ children: t("payment.checkout.skipPaymentMethod", {
269
+ defaultValue: "Skip, bind later"
270
+ })
271
+ })]
254
272
  }), state.message && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
255
273
  sx: {
256
274
  mt: 1,
@@ -277,6 +295,7 @@ function StripeCheckout({
277
295
  customer,
278
296
  onConfirm,
279
297
  onCancel,
298
+ onSkip = null,
280
299
  returnUrl = "",
281
300
  title = "",
282
301
  submitButtonText = ""
@@ -355,6 +374,7 @@ function StripeCheckout({
355
374
  mode,
356
375
  customer,
357
376
  onConfirm,
377
+ onSkip,
358
378
  returnUrl,
359
379
  submitButtonText
360
380
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/payment-react",
3
- "version": "1.26.3",
3
+ "version": "1.26.5",
4
4
  "description": "Reusable react components for payment kit v2",
5
5
  "keywords": [
6
6
  "react",
@@ -59,7 +59,7 @@
59
59
  "@arcblock/react-hooks": "^3.5.1",
60
60
  "@arcblock/ux": "^3.5.1",
61
61
  "@arcblock/ws": "^1.28.5",
62
- "@blocklet/payment-react-headless": "1.26.3",
62
+ "@blocklet/payment-react-headless": "1.26.5",
63
63
  "@blocklet/theme": "^3.5.1",
64
64
  "@blocklet/ui-react": "^3.5.1",
65
65
  "@mui/icons-material": "^7.1.2",
@@ -97,7 +97,7 @@
97
97
  "@babel/core": "^7.27.4",
98
98
  "@babel/preset-env": "^7.27.2",
99
99
  "@babel/preset-react": "^7.27.1",
100
- "@blocklet/payment-types": "1.26.3",
100
+ "@blocklet/payment-types": "1.26.5",
101
101
  "@storybook/addon-essentials": "^7.6.20",
102
102
  "@storybook/addon-interactions": "^7.6.20",
103
103
  "@storybook/addon-links": "^7.6.20",
@@ -128,5 +128,5 @@
128
128
  "vite-plugin-babel": "^1.3.1",
129
129
  "vite-plugin-node-polyfills": "^0.23.0"
130
130
  },
131
- "gitHead": "18c5d045139c572b52465e15c4c63b3e327efab5"
131
+ "gitHead": "2d208818d218494407989dd8cd460ab51d075952"
132
132
  }
@@ -59,6 +59,12 @@ export default function CheckoutDialogs() {
59
59
  returnUrl={getRedirectUrl(session)}
60
60
  onConfirm={submit.stripeConfirm}
61
61
  onCancel={submit.stripeCancel}
62
+ onSkip={
63
+ stripeContext!.noPaymentRequired &&
64
+ `${(session?.metadata as Record<string, any>)?.allow_skip_payment_method}` === 'true'
65
+ ? submit.stripeSkip
66
+ : undefined
67
+ }
62
68
  />
63
69
  )}
64
70
 
@@ -65,6 +65,13 @@ export default function CustomerInfoCard({ form, isLoggedIn }: CustomerInfoCardP
65
65
  [form.onChange] // eslint-disable-line react-hooks/exhaustive-deps
66
66
  );
67
67
 
68
+ // Auto-expand to edit mode when validation errors appear (e.g. after submit click)
69
+ useEffect(() => {
70
+ if (ready && !showEditForm && Object.keys(form.errors).length > 0) {
71
+ setShowEditForm(true);
72
+ }
73
+ }, [form.errors]); // eslint-disable-line react-hooks/exhaustive-deps
74
+
68
75
  if (!isLoggedIn || !ready) return null;
69
76
 
70
77
  // Summary view
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/indent */
1
2
  import { Box } from '@mui/material';
2
3
  import { alpha, useTheme } from '@mui/material/styles';
3
4
  import Header from '@blocklet/ui-react/lib/Header';
@@ -34,19 +35,19 @@ const isSafari =
34
35
 
35
36
  const slideInFromRight = isSafari
36
37
  ? ({
37
- '@keyframes panelFadeIn': {
38
- from: { opacity: 0, transform: 'translateX(24px)' },
39
- to: { opacity: 1, transform: 'none' },
40
- },
41
- animation: { xs: 'none', md: 'panelFadeIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) both' },
42
- } as const)
38
+ '@keyframes panelFadeIn': {
39
+ from: { opacity: 0, transform: 'translateX(24px)' },
40
+ to: { opacity: 1, transform: 'none' },
41
+ },
42
+ animation: { xs: 'none', md: 'panelFadeIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) both' },
43
+ } as const)
43
44
  : ({
44
- '@keyframes slideInRight': {
45
- from: { transform: 'translateX(100%)' },
46
- to: { transform: 'translateX(0)' },
47
- },
48
- animation: { xs: 'none', md: 'slideInRight 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards' },
49
- } as const);
45
+ '@keyframes slideInRight': {
46
+ from: { transform: 'translateX(100%)' },
47
+ to: { transform: 'translateX(0)' },
48
+ },
49
+ animation: { xs: 'none', md: 'slideInRight 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards' },
50
+ } as const);
50
51
 
51
52
  interface CheckoutLayoutProps {
52
53
  left: React.ReactNode;
@@ -136,9 +136,14 @@ export const whiteTooltipSx = {
136
136
  };
137
137
 
138
138
  // Helper: format trial text
139
- export function formatTrialText(t: (key: string) => string, days: number, interval: string): string {
140
- const intervalLabel = t(`common.${interval || 'day'}`);
141
- return `${days} ${intervalLabel}${days > 1 ? 's' : ''} free`;
139
+ export function formatTrialText(
140
+ t: (key: string, params?: Record<string, any>) => string,
141
+ days: number,
142
+ interval: string
143
+ ): string {
144
+ const key = interval || 'day';
145
+ const intervalKey = days > 1 ? `common.${key}s` : `common.${key}`;
146
+ return t('payment.checkout.free', { count: days, interval: t(intervalKey) });
142
147
  }
143
148
 
144
149
  // ── Item meta: badge / title / subtitle for the header area ──
@@ -10,12 +10,14 @@ export default function ServiceSuspendedDialog({ open, onClose }: { open: boolea
10
10
  <Dialog
11
11
  open={open}
12
12
  onClose={onClose}
13
- PaperProps={{
14
- sx: {
15
- borderRadius: 3,
16
- maxWidth: 400,
17
- mx: 'auto',
18
- overflow: 'hidden',
13
+ slotProps={{
14
+ paper: {
15
+ sx: {
16
+ borderRadius: 3,
17
+ maxWidth: 400,
18
+ mx: 'auto',
19
+ overflow: 'hidden',
20
+ },
19
21
  },
20
22
  }}>
21
23
  <DialogContent sx={{ p: 0 }}>
package/src/libs/util.ts CHANGED
@@ -1062,34 +1062,23 @@ export function getFreeTrialTime(
1062
1062
  interval: string;
1063
1063
  } {
1064
1064
  const now = dayjs().unix();
1065
+ const plural = (singular: string, count: number) => t(`common.${count > 1 ? `${singular}s` : singular}`, locale);
1065
1066
  if (trialEnd > 0 && trialEnd > now) {
1066
1067
  if (trialEnd - now < 3600) {
1067
- return {
1068
- count: Math.ceil((trialEnd - now) / 60),
1069
- interval: t('common.minute', locale),
1070
- };
1068
+ const count = Math.ceil((trialEnd - now) / 60);
1069
+ return { count, interval: plural('minute', count) };
1071
1070
  }
1072
1071
  if (trialEnd - now < 86400) {
1073
- return {
1074
- count: Math.ceil((trialEnd - now) / 3600),
1075
- interval: t('common.hour', locale),
1076
- };
1072
+ const count = Math.ceil((trialEnd - now) / 3600);
1073
+ return { count, interval: plural('hour', count) };
1077
1074
  }
1078
- return {
1079
- count: Math.ceil((trialEnd - now) / 86400),
1080
- interval: t('common.day', locale),
1081
- };
1075
+ const count = Math.ceil((trialEnd - now) / 86400);
1076
+ return { count, interval: plural('day', count) };
1082
1077
  }
1083
1078
  if (trialInDays > 0) {
1084
- return {
1085
- count: trialInDays,
1086
- interval: t('common.day', locale),
1087
- };
1079
+ return { count: trialInDays, interval: plural('day', trialInDays) };
1088
1080
  }
1089
- return {
1090
- count: 0,
1091
- interval: t('common.day', locale),
1092
- };
1081
+ return { count: 0, interval: t('common.day', locale) };
1093
1082
  }
1094
1083
  export function formatCheckoutHeadlines(
1095
1084
  items: TLineItemExpanded[],
@@ -100,7 +100,7 @@ export default flat({
100
100
  continue: 'Continue',
101
101
  qty: 'Qty {count}',
102
102
  each: '{unit} each',
103
- trial: "Free for {count} {interval}{count > 1 ? 's' : ''}",
103
+ trial: 'Free for {count} {interval}',
104
104
  billed: 'billed {rule}',
105
105
  metered: 'based on usage',
106
106
  minute: 'minute',
@@ -117,6 +117,7 @@ export default flat({
117
117
  month3: 'every 3 months',
118
118
  month6: 'every 6 months',
119
119
  recurring: 'every {count} {interval}',
120
+ minutes: 'minutes',
120
121
  hours: 'hours',
121
122
  days: 'days',
122
123
  weeks: 'weeks',
@@ -271,6 +272,7 @@ export default flat({
271
272
  configTip: 'Configure donation settings in Payment Kit',
272
273
  },
273
274
  cardPay: '{action} with bank card',
275
+ skipPaymentMethod: 'Skip, bind later',
274
276
  empty: 'Nothing to pay',
275
277
  per: 'per',
276
278
  pay: 'Pay {payee}',
@@ -281,7 +283,7 @@ export default flat({
281
283
  then: 'Then {subscription} {recurring}',
282
284
  meteredThen: 'Then {recurring} based on usage',
283
285
  metered: '{recurring} based on usage',
284
- free: "{count} {interval}{count > 1 ? 's' : ''} free",
286
+ free: '{count} {interval} free',
285
287
  least: 'continue with at least',
286
288
  completed: {
287
289
  payment: 'Purchase successful',
@@ -117,6 +117,7 @@ export default flat({
117
117
  month3: '每季度',
118
118
  month6: '每半年',
119
119
  recurring: '每{count}{interval}',
120
+ minutes: '分钟',
120
121
  hours: '小时',
121
122
  days: '天',
122
123
  weeks: '周',
@@ -267,6 +268,7 @@ export default flat({
267
268
  configTip: '前往 Payment Kit 配置打赏选项',
268
269
  },
269
270
  cardPay: '使用银行卡{action}',
271
+ skipPaymentMethod: '跳过,稍后绑定',
270
272
  empty: '没有可支付的项目',
271
273
  per: '每',
272
274
  pay: '付款给 {payee}',
@@ -1759,15 +1759,7 @@ export default function PaymentForm({
1759
1759
  }}
1760
1760
  />
1761
1761
  )}
1762
- {state.serviceSuspended && (
1763
- <ConfirmDialog
1764
- onConfirm={() => setState({ serviceSuspended: false })}
1765
- onCancel={() => setState({ serviceSuspended: false })}
1766
- title={t('payment.checkout.stopAcceptingOrders.title')}
1767
- message={t('payment.checkout.stopAcceptingOrders.description')}
1768
- confirm={t('common.confirm')}
1769
- />
1770
- )}
1762
+ {state.serviceSuspended && <ServiceSuspendedDialog open onClose={() => setState({ serviceSuspended: false })} />}
1771
1763
  {FastCheckoutConfirmDialog}
1772
1764
  {CreditInsufficientDialog}
1773
1765
  {PriceUpdatedDialog}
@@ -19,6 +19,7 @@ export type StripeCheckoutFormProps = {
19
19
  customer: TCustomer;
20
20
  mode: string;
21
21
  onConfirm: Function;
22
+ onSkip?: Function | null;
22
23
  returnUrl?: string;
23
24
  submitButtonText?: string;
24
25
  };
@@ -39,6 +40,7 @@ function StripeCheckoutForm({
39
40
  customer,
40
41
  mode,
41
42
  onConfirm,
43
+ onSkip = null,
42
44
  returnUrl = '',
43
45
  submitButtonText = '',
44
46
  }: StripeCheckoutFormProps) {
@@ -229,17 +231,30 @@ function StripeCheckoutForm({
229
231
  </Center>
230
232
  )}
231
233
  {stripe && elements && state.loaded && (
232
- <LoadingButton
233
- fullWidth
234
- sx={{ mt: 2, mb: 1, borderRadius: 0, fontSize: '0.875rem' }}
235
- type="submit"
236
- disabled={state.confirming || !state.loaded}
237
- loading={state.confirming}
238
- variant="contained"
239
- color="primary"
240
- size="large">
241
- {submitButtonText || t('payment.checkout.continue', { action: t(`payment.checkout.${mode}`) })}
242
- </LoadingButton>
234
+ <>
235
+ <LoadingButton
236
+ fullWidth
237
+ sx={{ mt: 2, mb: 1, borderRadius: 0, fontSize: '0.875rem' }}
238
+ type="submit"
239
+ disabled={state.confirming || !state.loaded}
240
+ loading={state.confirming}
241
+ variant="contained"
242
+ color="primary"
243
+ size="large">
244
+ {submitButtonText || t('payment.checkout.continue', { action: t(`payment.checkout.${mode}`) })}
245
+ </LoadingButton>
246
+ {onSkip && (
247
+ <LoadingButton
248
+ fullWidth
249
+ sx={{ mb: 1, borderRadius: 0, fontSize: '0.875rem', borderColor: 'divider' }}
250
+ disabled={state.confirming}
251
+ variant="outlined"
252
+ color="primary"
253
+ onClick={() => onSkip()}>
254
+ {t('payment.checkout.skipPaymentMethod', { defaultValue: 'Skip, bind later' })}
255
+ </LoadingButton>
256
+ )}
257
+ </>
243
258
  )}
244
259
  {state.message && <Typography sx={{ mt: 1, color: 'error.main' }}>{state.message}</Typography>}
245
260
  </Content>
@@ -264,6 +279,7 @@ export type StripeCheckoutProps = {
264
279
  customer: TCustomer;
265
280
  onConfirm: Function;
266
281
  onCancel: Function;
282
+ onSkip?: Function | null;
267
283
  returnUrl?: string;
268
284
  title?: string;
269
285
  submitButtonText?: string;
@@ -276,6 +292,7 @@ export default function StripeCheckout({
276
292
  customer,
277
293
  onConfirm,
278
294
  onCancel,
295
+ onSkip = null,
279
296
  returnUrl = '',
280
297
  title = '',
281
298
  submitButtonText = '',
@@ -349,6 +366,7 @@ export default function StripeCheckout({
349
366
  mode={mode}
350
367
  customer={customer}
351
368
  onConfirm={onConfirm}
369
+ onSkip={onSkip}
352
370
  returnUrl={returnUrl}
353
371
  submitButtonText={submitButtonText}
354
372
  />