@blocklet/payment-react 1.20.10 → 1.20.12

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 (41) hide show
  1. package/es/components/promotion-code.d.ts +19 -0
  2. package/es/components/promotion-code.js +153 -0
  3. package/es/contexts/payment.d.ts +8 -0
  4. package/es/contexts/payment.js +10 -1
  5. package/es/index.d.ts +2 -1
  6. package/es/index.js +3 -1
  7. package/es/libs/util.d.ts +5 -1
  8. package/es/libs/util.js +23 -0
  9. package/es/locales/en.js +40 -15
  10. package/es/locales/zh.js +29 -0
  11. package/es/payment/form/index.js +7 -1
  12. package/es/payment/index.js +19 -0
  13. package/es/payment/product-item.js +32 -3
  14. package/es/payment/summary.d.ts +5 -2
  15. package/es/payment/summary.js +193 -16
  16. package/lib/components/promotion-code.d.ts +19 -0
  17. package/lib/components/promotion-code.js +155 -0
  18. package/lib/contexts/payment.d.ts +8 -0
  19. package/lib/contexts/payment.js +13 -1
  20. package/lib/index.d.ts +2 -1
  21. package/lib/index.js +8 -0
  22. package/lib/libs/util.d.ts +5 -1
  23. package/lib/libs/util.js +29 -0
  24. package/lib/locales/en.js +40 -15
  25. package/lib/locales/zh.js +29 -0
  26. package/lib/payment/form/index.js +8 -1
  27. package/lib/payment/index.js +23 -0
  28. package/lib/payment/product-item.js +46 -0
  29. package/lib/payment/summary.d.ts +5 -2
  30. package/lib/payment/summary.js +153 -11
  31. package/package.json +9 -9
  32. package/src/components/promotion-code.tsx +184 -0
  33. package/src/contexts/payment.tsx +15 -0
  34. package/src/index.ts +2 -0
  35. package/src/libs/util.ts +35 -0
  36. package/src/locales/en.tsx +40 -15
  37. package/src/locales/zh.tsx +29 -0
  38. package/src/payment/form/index.tsx +10 -1
  39. package/src/payment/index.tsx +22 -0
  40. package/src/payment/product-item.tsx +37 -2
  41. package/src/payment/summary.tsx +201 -16
@@ -0,0 +1,19 @@
1
+ export interface AppliedPromoCode {
2
+ id: string;
3
+ code: string;
4
+ discount_amount?: string;
5
+ }
6
+ interface PromotionCodeProps {
7
+ checkoutSessionId: string;
8
+ initialAppliedCodes?: AppliedPromoCode[];
9
+ disabled?: boolean;
10
+ className?: string;
11
+ placeholder?: string;
12
+ currencyId: string;
13
+ onUpdate?: (data: {
14
+ appliedCodes: AppliedPromoCode[];
15
+ discountAmount: string;
16
+ }) => void;
17
+ }
18
+ export default function PromotionCode({ checkoutSessionId, initialAppliedCodes, disabled, className, placeholder, onUpdate, currencyId, }: PromotionCodeProps): import("react").JSX.Element;
19
+ export {};
@@ -0,0 +1,153 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
3
+ import { TextField, Button, Alert, Box, InputAdornment } from "@mui/material";
4
+ import { Add } from "@mui/icons-material";
5
+ import { useState } from "react";
6
+ import LoadingButton from "./loading-button.js";
7
+ import api from "../libs/api.js";
8
+ import { usePaymentContext } from "../contexts/payment.js";
9
+ export default function PromotionCode({
10
+ checkoutSessionId,
11
+ initialAppliedCodes = [],
12
+ disabled = false,
13
+ className = "",
14
+ placeholder = "",
15
+ onUpdate = void 0,
16
+ currencyId
17
+ }) {
18
+ const { t } = useLocaleContext();
19
+ const [showInput, setShowInput] = useState(false);
20
+ const [code, setCode] = useState("");
21
+ const [error, setError] = useState("");
22
+ const [applying, setApplying] = useState(false);
23
+ const [appliedCodes, setAppliedCodes] = useState(initialAppliedCodes);
24
+ const { session, paymentState } = usePaymentContext();
25
+ const handleLoginCheck = () => {
26
+ if (!session.user) {
27
+ session?.login(() => {
28
+ handleApply();
29
+ });
30
+ } else {
31
+ handleApply();
32
+ }
33
+ };
34
+ const handleApply = async () => {
35
+ if (!code.trim()) return;
36
+ if (paymentState.paying || paymentState.stripePaying) {
37
+ return;
38
+ }
39
+ setApplying(true);
40
+ setError("");
41
+ try {
42
+ const response = await api.post(`/api/checkout-sessions/${checkoutSessionId}/apply-promotion`, {
43
+ promotion_code: code.trim(),
44
+ currency_id: currencyId
45
+ });
46
+ const discounts = response.data.discounts || [];
47
+ const appliedDiscount = discounts[0];
48
+ if (appliedDiscount) {
49
+ const newCode = {
50
+ id: appliedDiscount.promotion_code || appliedDiscount.coupon,
51
+ code: code.trim(),
52
+ discount_amount: appliedDiscount.discount_amount
53
+ };
54
+ setAppliedCodes([newCode]);
55
+ setCode("");
56
+ setShowInput(false);
57
+ onUpdate?.({
58
+ appliedCodes: [newCode],
59
+ discountAmount: appliedDiscount.discount_amount
60
+ });
61
+ }
62
+ } catch (err) {
63
+ const errorMessage = err.response?.data?.error || err.message;
64
+ setError(errorMessage);
65
+ } finally {
66
+ setApplying(false);
67
+ }
68
+ };
69
+ const handleKeyPress = (event) => {
70
+ if (event.key === "Enter" && !applying && code.trim()) {
71
+ handleLoginCheck();
72
+ }
73
+ };
74
+ const isPaymentInProgress = paymentState.paying || paymentState.stripePaying;
75
+ return /* @__PURE__ */ jsx(Box, { className, children: appliedCodes.length === 0 && !disabled && !isPaymentInProgress && (showInput ? /* @__PURE__ */ jsxs(
76
+ Box,
77
+ {
78
+ onBlur: () => {
79
+ if (!code.trim()) {
80
+ setShowInput(false);
81
+ }
82
+ },
83
+ children: [
84
+ /* @__PURE__ */ jsx(
85
+ TextField,
86
+ {
87
+ fullWidth: true,
88
+ value: code,
89
+ onChange: (e) => setCode(e.target.value),
90
+ onKeyPress: handleKeyPress,
91
+ placeholder: placeholder || t("payment.checkout.promotion.placeholder"),
92
+ variant: "outlined",
93
+ size: "small",
94
+ disabled: applying,
95
+ autoFocus: true,
96
+ slotProps: {
97
+ input: {
98
+ endAdornment: /* @__PURE__ */ jsx(InputAdornment, { position: "end", children: /* @__PURE__ */ jsx(
99
+ LoadingButton,
100
+ {
101
+ size: "small",
102
+ onClick: handleLoginCheck,
103
+ loading: applying,
104
+ disabled: !code.trim(),
105
+ variant: "text",
106
+ sx: {
107
+ color: "primary.main",
108
+ fontSize: "small"
109
+ },
110
+ children: t("payment.checkout.promotion.apply")
111
+ }
112
+ ) })
113
+ }
114
+ },
115
+ sx: {
116
+ "& .MuiOutlinedInput-root": {
117
+ pr: 1
118
+ }
119
+ }
120
+ }
121
+ ),
122
+ error && /* @__PURE__ */ jsx(
123
+ Alert,
124
+ {
125
+ severity: "error",
126
+ sx: {
127
+ my: 1
128
+ },
129
+ children: error
130
+ }
131
+ )
132
+ ]
133
+ }
134
+ ) : /* @__PURE__ */ jsx(
135
+ Button,
136
+ {
137
+ onClick: () => setShowInput(true),
138
+ startIcon: /* @__PURE__ */ jsx(Add, { fontSize: "small" }),
139
+ variant: "text",
140
+ sx: {
141
+ fontWeight: "normal",
142
+ textTransform: "none",
143
+ justifyContent: "flex-start",
144
+ p: 0,
145
+ "&:hover": {
146
+ backgroundColor: "transparent",
147
+ textDecoration: "underline"
148
+ }
149
+ },
150
+ children: t("payment.checkout.promotion.add_code")
151
+ }
152
+ )) });
153
+ }
@@ -19,6 +19,14 @@ export type PaymentContextType = {
19
19
  api: Axios;
20
20
  payable: boolean;
21
21
  setPayable: (status: boolean) => void;
22
+ paymentState: {
23
+ paying: boolean;
24
+ stripePaying: boolean;
25
+ };
26
+ setPaymentState: (state: Partial<{
27
+ paying: boolean;
28
+ stripePaying: boolean;
29
+ }>) => void;
22
30
  };
23
31
  export type PaymentContextProps = {
24
32
  session: import('@arcblock/did-connect-react/lib/types').SessionContext['session'];
@@ -125,6 +125,13 @@ function PaymentProvider({
125
125
  }, [session?.user]);
126
126
  const prefix = getPrefix();
127
127
  const [payable, setPayable] = useState(true);
128
+ const [paymentState, setPaymentState] = useState({
129
+ paying: false,
130
+ stripePaying: false
131
+ });
132
+ const updatePaymentState = (state) => {
133
+ setPaymentState((prev) => ({ ...prev, ...state }));
134
+ };
128
135
  if (error) {
129
136
  return /* @__PURE__ */ jsx(Alert, { severity: "error", children: error.message });
130
137
  }
@@ -146,7 +153,9 @@ function PaymentProvider({
146
153
  setLivemode,
147
154
  api,
148
155
  payable,
149
- setPayable
156
+ setPayable,
157
+ paymentState,
158
+ setPaymentState: updatePaymentState
150
159
  },
151
160
  children
152
161
  }
package/es/index.d.ts CHANGED
@@ -39,6 +39,7 @@ import DateRangePicker from './components/date-range-picker';
39
39
  import AutoTopupModal from './components/auto-topup/modal';
40
40
  import AutoTopup from './components/auto-topup';
41
41
  import Collapse from './components/collapse';
42
+ import PromotionCode from './components/promotion-code';
42
43
  export { PaymentThemeProvider } from './theme';
43
44
  export * from './libs/util';
44
45
  export * from './libs/connect';
@@ -53,4 +54,4 @@ export * from './hooks/scroll';
53
54
  export * from './hooks/keyboard';
54
55
  export * from './libs/validator';
55
56
  export { translations, createTranslator } from './locales';
56
- export { createLazyComponent, api, dayjs, FormInput, FormLabel, PhoneInput, AddressForm, StripeForm, Status, Livemode, Switch, ConfirmDialog, CheckoutForm, CheckoutTable, CheckoutDonate, CurrencySelector, Payment, PaymentSummary, PricingTable, ProductSkeleton, Amount, CustomerInvoiceList, CustomerPaymentList, TxLink, TxGas, SafeGuard, PricingItem, CountrySelect, Table, TruncatedText, Link, OverdueInvoicePayment, PaymentBeneficiaries, LoadingButton, DonateDetails, ResumeSubscription, CreditGrantsList, CreditTransactionsList, DateRangePicker, CreditStatusChip, AutoTopupModal, AutoTopup, Collapse, };
57
+ export { createLazyComponent, api, dayjs, FormInput, FormLabel, PhoneInput, AddressForm, StripeForm, Status, Livemode, Switch, ConfirmDialog, CheckoutForm, CheckoutTable, CheckoutDonate, CurrencySelector, Payment, PaymentSummary, PricingTable, ProductSkeleton, Amount, CustomerInvoiceList, CustomerPaymentList, TxLink, TxGas, SafeGuard, PricingItem, CountrySelect, Table, TruncatedText, Link, OverdueInvoicePayment, PaymentBeneficiaries, LoadingButton, DonateDetails, ResumeSubscription, CreditGrantsList, CreditTransactionsList, DateRangePicker, CreditStatusChip, AutoTopupModal, AutoTopup, Collapse, PromotionCode, };
package/es/index.js CHANGED
@@ -39,6 +39,7 @@ import DateRangePicker from "./components/date-range-picker.js";
39
39
  import AutoTopupModal from "./components/auto-topup/modal.js";
40
40
  import AutoTopup from "./components/auto-topup/index.js";
41
41
  import Collapse from "./components/collapse.js";
42
+ import PromotionCode from "./components/promotion-code.js";
42
43
  export { PaymentThemeProvider } from "./theme/index.js";
43
44
  export * from "./libs/util.js";
44
45
  export * from "./libs/connect.js";
@@ -96,5 +97,6 @@ export {
96
97
  CreditStatusChip,
97
98
  AutoTopupModal,
98
99
  AutoTopup,
99
- Collapse
100
+ Collapse,
101
+ PromotionCode
100
102
  };
package/es/libs/util.d.ts CHANGED
@@ -1,6 +1,10 @@
1
- import type { PaymentDetails, PriceCurrency, PriceRecurring, TInvoiceExpanded, TLineItemExpanded, TPaymentCurrency, TPaymentCurrencyExpanded, TPaymentMethod, TPaymentMethodExpanded, TPrice, TProductExpanded, TSubscriptionExpanded, TSubscriptionItemExpanded } from '@blocklet/payment-types';
1
+ import type { PaymentDetails, PriceCurrency, PriceRecurring, TCoupon, TInvoiceExpanded, TLineItemExpanded, TPaymentCurrency, TPaymentCurrencyExpanded, TPaymentMethod, TPaymentMethodExpanded, TPrice, TProductExpanded, TSubscriptionExpanded, TSubscriptionItemExpanded } from '@blocklet/payment-types';
2
2
  import type { ActionProps, PricingRenderProps } from '../types';
3
3
  export declare const PAYMENT_KIT_DID = "z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk";
4
+ /**
5
+ * Format coupon discount terms for display
6
+ */
7
+ export declare const formatCouponTerms: (coupon: TCoupon, currency: TPaymentCurrency, locale?: string) => string;
4
8
  export declare const isPaymentKitMounted: () => any;
5
9
  export declare const getPrefix: () => string;
6
10
  export declare function isCrossOrigin(): boolean;
package/es/libs/util.js CHANGED
@@ -8,6 +8,29 @@ import { joinURL, withQuery } from "ufo";
8
8
  import { t } from "../locales/index.js";
9
9
  import dayjs from "./dayjs.js";
10
10
  export const PAYMENT_KIT_DID = "z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk";
11
+ export const formatCouponTerms = (coupon, currency, locale = "en") => {
12
+ let couponOff = "";
13
+ if (coupon.percent_off && coupon.percent_off > 0) {
14
+ couponOff = t("payment.checkout.coupon.percentage", locale, { percent: coupon.percent_off });
15
+ }
16
+ if (coupon.amount_off && coupon.amount_off !== "0") {
17
+ const { symbol } = currency;
18
+ couponOff = coupon.currency_id === currency.id ? coupon.amount_off || "" : coupon.currency_options?.[currency.id]?.amount_off || "";
19
+ if (couponOff) {
20
+ couponOff = t("payment.checkout.coupon.fixedAmount", locale, {
21
+ amount: formatAmount(couponOff, currency.decimal),
22
+ symbol
23
+ });
24
+ }
25
+ }
26
+ if (!couponOff) {
27
+ return t("payment.checkout.coupon.noDiscount");
28
+ }
29
+ return t(`payment.checkout.coupon.terms.${coupon.duration}`, locale, {
30
+ couponOff,
31
+ months: coupon.duration_in_months || 0
32
+ });
33
+ };
11
34
  export const isPaymentKitMounted = () => {
12
35
  return (window.blocklet?.componentMountPoints || []).some((x) => x.did === PAYMENT_KIT_DID);
13
36
  };
package/es/locales/en.js CHANGED
@@ -102,8 +102,8 @@ export default flat({
102
102
  know: "I know",
103
103
  relatedSubscription: "Subscription",
104
104
  connect: {
105
- defaultScan: "Use following methods to complete this action",
106
- scan: "Use following methods to complete this {action}",
105
+ defaultScan: "Use the following methods to complete this action",
106
+ scan: "Use the following methods to complete this {action}",
107
107
  confirm: "Confirm",
108
108
  cancel: "Cancel"
109
109
  },
@@ -153,14 +153,14 @@ export default flat({
153
153
  },
154
154
  inactive: "Donation feature is not enabled",
155
155
  enable: "Enable Donation",
156
- enableSuccess: "Enable Success",
157
- configPrompt: "Donation feature is enabled, you can configure donation options in Payment Kit",
156
+ enableSuccess: "Successfully enabled",
157
+ configPrompt: "The donation feature is enabled. You can configure donation options in Payment Kit",
158
158
  configNow: "Configure Now",
159
159
  later: "Configure Later",
160
160
  configTip: "Configure donation settings in Payment Kit"
161
161
  },
162
162
  cardPay: "{action} with bank card",
163
- empty: "No thing to pay",
163
+ empty: "Nothing to pay",
164
164
  per: "per",
165
165
  pay: "Pay {payee}",
166
166
  try1: "Try {name}",
@@ -174,8 +174,8 @@ export default flat({
174
174
  least: "continue with at least",
175
175
  completed: {
176
176
  payment: "Thanks for your purchase",
177
- subscription: "Thanks for your subscribing",
178
- setup: "Thanks for your subscribing",
177
+ subscription: "Thanks for subscribing",
178
+ setup: "Thanks for subscribing",
179
179
  donate: "Thanks for your tip",
180
180
  tip: "A payment to {payee} has been completed. You can view the details of this payment in your account."
181
181
  },
@@ -184,7 +184,7 @@ export default flat({
184
184
  progress: "Progress {progress}%",
185
185
  delivered: "Installation completed",
186
186
  failed: "Processing failed",
187
- failedMsg: "An exception occurred during installation. We will automatically process a refund for you. We apologize for the inconvenience caused. Thank you for your understanding!"
187
+ failedMsg: "An exception occurred during installation. We will automatically process a refund for you. We apologize for the inconvenience. Thank you for your understanding!"
188
188
  },
189
189
  confirm: {
190
190
  withStake: "By confirming, you allow {payee} to charge your account for future payments and, if necessary, slash your stake. You can cancel your subscription or withdraw your stake at any time.",
@@ -233,14 +233,39 @@ export default flat({
233
233
  },
234
234
  emptyItems: {
235
235
  title: "Nothing to show here",
236
- description: "Seems this checkoutSession is not configured properly"
236
+ description: "It seems this checkout session is not configured properly"
237
237
  },
238
238
  orderSummary: "Order Summary",
239
239
  paymentDetails: "Payment Details",
240
240
  productListTotal: "Includes {total} items",
241
+ promotion: {
242
+ add_code: "Add promotion code",
243
+ enter_code: "Enter promotion code",
244
+ placeholder: "Enter code",
245
+ apply: "Apply",
246
+ applied: "Applied promotion codes",
247
+ dialog: {
248
+ title: "Add promotion code"
249
+ },
250
+ error: {
251
+ unknown: "Unknown error",
252
+ network: "Network error occurred",
253
+ removal: "Failed to remove code"
254
+ }
255
+ },
256
+ coupon: {
257
+ noDiscount: "No discount",
258
+ percentage: "{percent}% off",
259
+ fixedAmount: "{amount} {symbol} off",
260
+ terms: {
261
+ forever: "{couponOff} forever",
262
+ once: "{couponOff} once",
263
+ repeating: "{couponOff} for {months} month{months > 1 ? 's' : ''}"
264
+ }
265
+ },
241
266
  connectModal: {
242
267
  title: "{action}",
243
- scan: "Use following methods to complete this payment",
268
+ scan: "Use the following methods to complete this payment",
244
269
  confirm: "Confirm",
245
270
  cancel: "Cancel"
246
271
  },
@@ -323,7 +348,7 @@ export default flat({
323
348
  summary: "Summary",
324
349
  products: "Products",
325
350
  update: "Update Information",
326
- empty: "Seems you do not have any subscriptions or payments here",
351
+ empty: "It seems you do not have any subscriptions or payments here",
327
352
  cancel: {
328
353
  button: "Unsubscribe",
329
354
  title: "Cancel your subscription",
@@ -334,7 +359,7 @@ export default flat({
334
359
  tip: "We would love your feedback, it will help us improve our service",
335
360
  too_expensive: "The service is too expensive",
336
361
  missing_features: "Features are missing for this service",
337
- switched_service: "I have switched to alternative service",
362
+ switched_service: "I have switched to an alternative service",
338
363
  unused: "I no longer use this service",
339
364
  customer_service: "The customer service is poor",
340
365
  too_complex: "The service is too complex to use",
@@ -345,7 +370,7 @@ export default flat({
345
370
  pastDue: {
346
371
  button: "Pay",
347
372
  invoices: "Past Due Invoices",
348
- warning: "Past due invoices need to be paid immediately, otherwise you can not make new purchases anymore.",
373
+ warning: "Past due invoices need to be paid immediately, otherwise you cannot make new purchases anymore.",
349
374
  alert: {
350
375
  title: "You have unpaid invoices",
351
376
  customMessage: "Please pay immediately, otherwise new purchases or subscriptions will be prohibited.",
@@ -398,7 +423,7 @@ export default flat({
398
423
  amountApplied: "Applied Credit",
399
424
  pay: "Pay this invoice",
400
425
  paySuccess: "You have successfully paid the invoice",
401
- payError: "Failed to paid the invoice",
426
+ payError: "Failed to pay the invoice",
402
427
  renew: "Renew the subscription",
403
428
  renewSuccess: "You have successfully renewed the subscription",
404
429
  renewError: "Failed to renew the subscription",
@@ -426,7 +451,7 @@ export default flat({
426
451
  viewAll: "View all",
427
452
  empty: "There are no subscriptions here",
428
453
  changePayment: "Change payment method",
429
- trialLeft: "Trail Left",
454
+ trialLeft: "Trial Left",
430
455
  owner: "Subscription Owner"
431
456
  },
432
457
  overdue: {
package/es/locales/zh.js CHANGED
@@ -226,6 +226,35 @@ export default flat({
226
226
  add: "\u6DFB\u52A0\u5230\u8BA2\u5355",
227
227
  remove: "\u4ECE\u8BA2\u5355\u79FB\u9664"
228
228
  },
229
+ promotion: {
230
+ add_code: "\u6DFB\u52A0\u4FC3\u9500\u7801",
231
+ enter_code: "\u8F93\u5165\u4FC3\u9500\u7801",
232
+ apply: "\u5E94\u7528",
233
+ applied: "\u5DF2\u5E94\u7528\u7684\u4FC3\u9500\u7801",
234
+ placeholder: "\u8F93\u5165\u4FC3\u9500\u7801",
235
+ duration_once: "1\u6B21\u4F18\u60E0 {amount} {symbol}",
236
+ duration_repeating: "{months}\u4E2A\u6708\u4F18\u60E0 {amount} {symbol}",
237
+ duration_forever: "\u6C38\u4E45\u4F18\u60E0 {amount} {symbol}",
238
+ dialog: {
239
+ title: "\u6DFB\u52A0\u4FC3\u9500\u7801"
240
+ },
241
+ error: {
242
+ invalid: "\u65E0\u6548\u7684\u4FC3\u9500\u7801",
243
+ expired: "\u4FC3\u9500\u7801\u5DF2\u8FC7\u671F",
244
+ used: "\u4FC3\u9500\u7801\u5DF2\u88AB\u4F7F\u7528",
245
+ not_applicable: "\u4FC3\u9500\u7801\u4E0D\u9002\u7528\u4E8E\u6B64\u8BA2\u5355"
246
+ }
247
+ },
248
+ coupon: {
249
+ noDiscount: "\u65E0\u4F18\u60E0",
250
+ percentage: "{percent}%",
251
+ fixedAmount: "{amount} {symbol}",
252
+ terms: {
253
+ forever: "\u6C38\u4E45\u4EAB {couponOff} \u6298\u6263",
254
+ once: "\u5355\u6B21\u4EAB {couponOff} \u6298\u6263",
255
+ repeating: "{months} \u4E2A\u6708\u5185\u4EAB {couponOff} \u6298\u6263"
256
+ }
257
+ },
229
258
  credit: {
230
259
  oneTimeInfo: "\u4ED8\u6B3E\u5B8C\u6210\u540E\u60A8\u5C06\u83B7\u5F97 {amount} {symbol} \u989D\u5EA6",
231
260
  recurringInfo: "\u60A8\u5C06{period}\u83B7\u5F97 {amount} {symbol} \u989D\u5EA6",
@@ -103,7 +103,7 @@ export default function PaymentForm({
103
103
  }) {
104
104
  const { t, locale } = useLocaleContext();
105
105
  const { isMobile } = useMobile();
106
- const { session, connect, payable } = usePaymentContext();
106
+ const { session, connect, payable, setPaymentState } = usePaymentContext();
107
107
  const subscription = useSubscription("events");
108
108
  const formErrorPosition = "bottom";
109
109
  const {
@@ -150,6 +150,12 @@ export default function PaymentForm({
150
150
  subscription.on("checkout.session.completed", onCheckoutComplete);
151
151
  }
152
152
  }, [subscription]);
153
+ useEffect(() => {
154
+ setPaymentState({
155
+ paying: state.submitting || state.paying,
156
+ stripePaying: state.stripePaying
157
+ });
158
+ }, [state.submitting, state.paying, state.stripePaying]);
153
159
  const mergeUserInfo = (customerInfo, userInfo) => {
154
160
  return {
155
161
  ...userInfo || {},
@@ -139,6 +139,13 @@ function PaymentInner({
139
139
  if (onChange) {
140
140
  onChange(methods.getValues());
141
141
  }
142
+ if (state.checkoutSession?.discounts?.length) {
143
+ api.post(`/api/checkout-sessions/${state.checkoutSession.id}/recalculate-promotion`, {
144
+ currency_id: currencyId
145
+ }).then(() => {
146
+ onPromotionUpdate();
147
+ });
148
+ }
142
149
  }, [currencyId]);
143
150
  const onUpsell = async (from, to) => {
144
151
  try {
@@ -200,6 +207,15 @@ function PaymentInner({
200
207
  Toast.error(formatError(err));
201
208
  }
202
209
  };
210
+ const onPromotionUpdate = async () => {
211
+ try {
212
+ const { data } = await api.get(`/api/checkout-sessions/retrieve/${state.checkoutSession.id}`);
213
+ setState({ checkoutSession: data.checkoutSession });
214
+ } catch (err) {
215
+ console.error(err);
216
+ Toast.error(formatError(err));
217
+ }
218
+ };
203
219
  const handlePaid = (result) => {
204
220
  setState({ checkoutSession: result.checkoutSession });
205
221
  onPaid(result);
@@ -238,6 +254,9 @@ function PaymentInner({
238
254
  donationSettings: paymentLink?.donation_settings,
239
255
  action,
240
256
  completed,
257
+ checkoutSession: state.checkoutSession,
258
+ onPromotionUpdate,
259
+ paymentMethods,
241
260
  showFeatures
242
261
  }
243
262
  ),
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
3
- import { Box, Stack, Typography, IconButton, TextField, Alert } from "@mui/material";
4
- import { Add, Remove } from "@mui/icons-material";
3
+ import { Box, Stack, Typography, IconButton, TextField, Alert, Chip } from "@mui/material";
4
+ import { Add, Remove, LocalOffer } from "@mui/icons-material";
5
5
  import { useMemo, useState } from "react";
6
6
  import Status from "../components/status.js";
7
7
  import Switch from "../components/switch-button.js";
@@ -12,7 +12,8 @@ import {
12
12
  formatPrice,
13
13
  formatQuantityInventory,
14
14
  formatRecurring,
15
- formatUpsellSaving
15
+ formatUpsellSaving,
16
+ formatAmount
16
17
  } from "../libs/util.js";
17
18
  import ProductCard from "./product-card.js";
18
19
  import dayjs from "../libs/dayjs.js";
@@ -183,6 +184,34 @@ export default function ProductItem({
183
184
  ]
184
185
  }
185
186
  ),
187
+ item.discount_amounts && item.discount_amounts.length > 0 && /* @__PURE__ */ jsx(Stack, { direction: "row", spacing: 1, sx: { mt: 1, alignItems: "center" }, children: item.discount_amounts.map((discountAmount) => /* @__PURE__ */ jsx(
188
+ Chip,
189
+ {
190
+ icon: /* @__PURE__ */ jsx(LocalOffer, { sx: { fontSize: "0.8rem !important" } }),
191
+ label: /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 0.5 }, children: [
192
+ /* @__PURE__ */ jsx(Typography, { component: "span", sx: { fontSize: "0.75rem", fontWeight: "medium" }, children: discountAmount.promotion_code?.code || "DISCOUNT" }),
193
+ /* @__PURE__ */ jsxs(Typography, { component: "span", sx: { fontSize: "0.75rem" }, children: [
194
+ "(-",
195
+ formatAmount(discountAmount.amount || "0", currency.decimal),
196
+ " ",
197
+ currency.symbol,
198
+ ")"
199
+ ] })
200
+ ] }),
201
+ size: "small",
202
+ variant: "filled",
203
+ sx: {
204
+ height: 20,
205
+ "& .MuiChip-icon": {
206
+ color: "warning.main"
207
+ },
208
+ "& .MuiChip-label": {
209
+ px: 1
210
+ }
211
+ }
212
+ },
213
+ discountAmount.promotion_code
214
+ )) }),
186
215
  showFeatures && features.length > 0 && /* @__PURE__ */ jsx(
187
216
  Box,
188
217
  {
@@ -1,4 +1,4 @@
1
- import type { DonationSettings, TLineItemExpanded, TPaymentCurrency } from '@blocklet/payment-types';
1
+ import type { DonationSettings, TLineItemExpanded, TPaymentCurrency, TCheckoutSession, TPaymentMethodExpanded } from '@blocklet/payment-types';
2
2
  type Props = {
3
3
  items: TLineItemExpanded[];
4
4
  currency: TPaymentCurrency;
@@ -17,7 +17,10 @@ type Props = {
17
17
  donationSettings?: DonationSettings;
18
18
  action?: string;
19
19
  completed?: boolean;
20
+ checkoutSession?: TCheckoutSession;
21
+ onPromotionUpdate?: () => void;
22
+ paymentMethods?: TPaymentMethodExpanded[];
20
23
  showFeatures?: boolean;
21
24
  };
22
- export default function PaymentSummary({ items, currency, trialInDays, billingThreshold, onUpsell, onDownsell, onQuantityChange, onApplyCrossSell, onCancelCrossSell, onChangeAmount, checkoutSessionId, crossSellBehavior, showStaking, donationSettings, action, trialEnd, completed, showFeatures, ...rest }: Props): import("react").JSX.Element;
25
+ export default function PaymentSummary({ items, currency, trialInDays, billingThreshold, onUpsell, onDownsell, onQuantityChange, onApplyCrossSell, onCancelCrossSell, onChangeAmount, checkoutSessionId, crossSellBehavior, showStaking, donationSettings, action, trialEnd, completed, checkoutSession, paymentMethods, onPromotionUpdate, showFeatures, ...rest }: Props): import("react").JSX.Element;
23
26
  export {};