@blocklet/payment-react 1.20.11 → 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 +25 -0
  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 +25 -0
  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 +25 -0
  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
@@ -1,7 +1,7 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
3
- import { HelpOutline } from "@mui/icons-material";
4
- import { Box, Divider, Fade, Grow, Stack, Tooltip, Typography, Collapse, IconButton } from "@mui/material";
3
+ import { HelpOutline, Close, LocalOffer } from "@mui/icons-material";
4
+ import { Box, Divider, Fade, Grow, Stack, Tooltip, Typography, Collapse, IconButton, Button } from "@mui/material";
5
5
  import { BN, fromTokenToUnit, fromUnitToToken } from "@ocap/util";
6
6
  import { useRequest, useSetState } from "ahooks";
7
7
  import noop from "lodash/noop";
@@ -10,7 +10,14 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
10
10
  import { styled } from "@mui/material/styles";
11
11
  import Status from "../components/status.js";
12
12
  import api from "../libs/api.js";
13
- import { formatAmount, formatCheckoutHeadlines, getPriceUintAmountByCurrency } from "../libs/util.js";
13
+ import {
14
+ formatAmount,
15
+ formatCheckoutHeadlines,
16
+ getPriceUintAmountByCurrency,
17
+ formatCouponTerms,
18
+ formatNumber,
19
+ findCurrency
20
+ } from "../libs/util.js";
14
21
  import PaymentAmount from "./amount.js";
15
22
  import ProductDonation from "./product-donation.js";
16
23
  import ProductItem from "./product-item.js";
@@ -18,6 +25,7 @@ import Livemode from "../components/livemode.js";
18
25
  import { usePaymentContext } from "../contexts/payment.js";
19
26
  import { useMobile } from "../hooks/mobile.js";
20
27
  import LoadingButton from "../components/loading-button.js";
28
+ import PromotionCode from "../components/promotion-code.js";
21
29
  const ExpandMore = styled((props) => {
22
30
  const { expand, ...other } = props;
23
31
  return /* @__PURE__ */ jsx(IconButton, { ...other });
@@ -84,21 +92,58 @@ export default function PaymentSummary({
84
92
  action = "",
85
93
  trialEnd = 0,
86
94
  completed = false,
95
+ checkoutSession = void 0,
96
+ paymentMethods = [],
97
+ onPromotionUpdate = noop,
87
98
  showFeatures = false,
88
99
  ...rest
89
100
  }) {
90
101
  const { t, locale } = useLocaleContext();
91
102
  const { isMobile } = useMobile();
92
- const settings = usePaymentContext();
103
+ const { paymentState, ...settings } = usePaymentContext();
93
104
  const [state, setState] = useSetState({ loading: false, shake: false, expanded: items?.length < 3 });
94
105
  const { data, runAsync } = useRequest(
95
106
  () => checkoutSessionId ? fetchCrossSell(checkoutSessionId) : Promise.resolve(null)
96
107
  );
97
- const headlines = formatCheckoutHeadlines(items, currency, { trialEnd, trialInDays }, locale);
98
- const staking = showStaking ? getStakingSetup(items, currency, billingThreshold) : "0";
108
+ const sessionDiscounts = checkoutSession?.discounts || [];
109
+ const allowPromotionCodes = !!checkoutSession?.allow_promotion_codes;
110
+ const hasDiscounts = sessionDiscounts?.length > 0;
111
+ const discountCurrency = paymentMethods && checkoutSession ? findCurrency(
112
+ paymentMethods,
113
+ hasDiscounts ? checkoutSession?.currency_id || currency.id : currency.id
114
+ ) || settings.settings?.baseCurrency : currency;
115
+ const headlines = formatCheckoutHeadlines(items, discountCurrency, { trialEnd, trialInDays }, locale);
116
+ const staking = showStaking ? getStakingSetup(items, discountCurrency, billingThreshold) : "0";
117
+ const getAppliedPromotionCodes = () => {
118
+ if (!sessionDiscounts?.length) return [];
119
+ return sessionDiscounts.map((discount) => ({
120
+ id: discount.promotion_code || discount.coupon,
121
+ code: discount.verification_data?.code || "APPLIED",
122
+ discount_amount: discount.discount_amount
123
+ }));
124
+ };
125
+ const handlePromotionUpdate = () => {
126
+ onPromotionUpdate?.();
127
+ };
128
+ const handleRemovePromotion = async (sessionId) => {
129
+ if (paymentState.paying || paymentState.stripePaying) {
130
+ return;
131
+ }
132
+ try {
133
+ await api.delete(`/api/checkout-sessions/${sessionId}/remove-promotion`);
134
+ onPromotionUpdate?.();
135
+ } catch (err) {
136
+ console.error("Failed to remove promotion code:", err);
137
+ }
138
+ };
139
+ const discountAmount = new BN(checkoutSession?.total_details?.amount_discount || "0");
140
+ const subtotalAmount = fromUnitToToken(
141
+ new BN(fromTokenToUnit(headlines.actualAmount, discountCurrency?.decimal)).add(new BN(staking)).toString(),
142
+ discountCurrency?.decimal
143
+ );
99
144
  const totalAmount = fromUnitToToken(
100
- new BN(fromTokenToUnit(headlines.actualAmount, currency?.decimal)).add(new BN(staking)).toString(),
101
- currency?.decimal
145
+ new BN(fromTokenToUnit(subtotalAmount, discountCurrency?.decimal)).sub(discountAmount).toString(),
146
+ discountCurrency?.decimal
102
147
  );
103
148
  useBus(
104
149
  "error.REQUIRE_CROSS_SELL",
@@ -160,9 +205,9 @@ export default function PaymentSummary({
160
205
  item: x,
161
206
  settings: donationSettings,
162
207
  onChange: onChangeAmount,
163
- currency
208
+ currency: discountCurrency
164
209
  },
165
- `${x.price_id}-${currency.id}`
210
+ `${x.price_id}-${discountCurrency.id}`
166
211
  ) : /* @__PURE__ */ jsx(
167
212
  ProductItem,
168
213
  {
@@ -170,7 +215,7 @@ export default function PaymentSummary({
170
215
  items,
171
216
  trialInDays,
172
217
  trialEnd,
173
- currency,
218
+ currency: discountCurrency,
174
219
  onUpsell: handleUpsell,
175
220
  onDownsell: handleDownsell,
176
221
  adjustableQuantity: x.adjustable_quantity,
@@ -205,7 +250,7 @@ export default function PaymentSummary({
205
250
  }
206
251
  )
207
252
  },
208
- `${x.price_id}-${currency.id}`
253
+ `${x.price_id}-${discountCurrency.id}`
209
254
  )
210
255
  ) }),
211
256
  data && items.some((x) => x.price_id === data.id) === false && /* @__PURE__ */ jsx(Grow, { in: true, children: /* @__PURE__ */ jsx(
@@ -220,7 +265,7 @@ export default function PaymentSummary({
220
265
  item: { quantity: 1, price: data, price_id: data.id, cross_sell: true },
221
266
  items,
222
267
  trialInDays,
223
- currency,
268
+ currency: discountCurrency,
224
269
  trialEnd,
225
270
  onUpsell: noop,
226
271
  onDownsell: noop,
@@ -362,14 +407,146 @@ export default function PaymentSummary({
362
407
  }
363
408
  ),
364
409
  /* @__PURE__ */ jsxs(Typography, { children: [
365
- formatAmount(staking, currency.decimal),
410
+ formatAmount(staking, discountCurrency.decimal),
366
411
  " ",
367
- currency.symbol
412
+ discountCurrency.symbol
368
413
  ] })
369
414
  ]
370
415
  }
371
416
  )
372
417
  ] }),
418
+ (allowPromotionCodes || hasDiscounts) && /* @__PURE__ */ jsxs(
419
+ Stack,
420
+ {
421
+ direction: "row",
422
+ spacing: 1,
423
+ sx: {
424
+ justifyContent: "space-between",
425
+ alignItems: "center",
426
+ ...staking > 0 && {
427
+ borderTop: "1px solid",
428
+ borderColor: "divider",
429
+ pt: 1,
430
+ mt: 1
431
+ }
432
+ },
433
+ children: [
434
+ /* @__PURE__ */ jsx(Typography, { className: "base-label", children: t("common.subtotal") }),
435
+ /* @__PURE__ */ jsxs(Typography, { children: [
436
+ formatNumber(subtotalAmount),
437
+ " ",
438
+ discountCurrency.symbol
439
+ ] })
440
+ ]
441
+ }
442
+ ),
443
+ allowPromotionCodes && !hasDiscounts && /* @__PURE__ */ jsx(Box, { sx: { mt: 1 }, children: /* @__PURE__ */ jsx(
444
+ PromotionCode,
445
+ {
446
+ checkoutSessionId: checkoutSession.id,
447
+ initialAppliedCodes: getAppliedPromotionCodes(),
448
+ disabled: completed,
449
+ onUpdate: handlePromotionUpdate,
450
+ currencyId: currency.id
451
+ }
452
+ ) }),
453
+ hasDiscounts && /* @__PURE__ */ jsx(
454
+ Box,
455
+ {
456
+ sx: {
457
+ py: 1.5
458
+ },
459
+ children: sessionDiscounts.map((discount) => {
460
+ const promotionCodeInfo = discount.promotion_code_details;
461
+ const couponInfo = discount.coupon_details;
462
+ const discountDescription = couponInfo ? formatCouponTerms(couponInfo, discountCurrency, locale) : "";
463
+ const notSupported = discountDescription === t("payment.checkout.coupon.noDiscount");
464
+ return /* @__PURE__ */ jsxs(Stack, { children: [
465
+ /* @__PURE__ */ jsxs(
466
+ Stack,
467
+ {
468
+ direction: "row",
469
+ spacing: 1,
470
+ sx: {
471
+ justifyContent: "space-between",
472
+ alignItems: "center"
473
+ },
474
+ children: [
475
+ /* @__PURE__ */ jsxs(
476
+ Stack,
477
+ {
478
+ direction: "row",
479
+ spacing: 1,
480
+ sx: {
481
+ alignItems: "center",
482
+ backgroundColor: "grey.100",
483
+ width: "fit-content",
484
+ px: 1,
485
+ py: 0.5,
486
+ borderRadius: 1
487
+ },
488
+ children: [
489
+ /* @__PURE__ */ jsxs(
490
+ Typography,
491
+ {
492
+ sx: {
493
+ fontWeight: "medium",
494
+ fontSize: "small",
495
+ display: "flex",
496
+ alignItems: "center",
497
+ gap: 0.5
498
+ },
499
+ children: [
500
+ /* @__PURE__ */ jsx(LocalOffer, { sx: { color: "warning.main", fontSize: "small" } }),
501
+ promotionCodeInfo?.code || discount.verification_data?.code || t("payment.checkout.discount")
502
+ ]
503
+ }
504
+ ),
505
+ !completed && /* @__PURE__ */ jsx(
506
+ Button,
507
+ {
508
+ size: "small",
509
+ disabled: paymentState.paying || paymentState.stripePaying,
510
+ onClick: () => handleRemovePromotion(checkoutSessionId),
511
+ sx: {
512
+ minWidth: "auto",
513
+ width: 16,
514
+ height: 16,
515
+ color: "text.secondary",
516
+ "&.Mui-disabled": {
517
+ color: "text.disabled"
518
+ }
519
+ },
520
+ children: /* @__PURE__ */ jsx(Close, { sx: { fontSize: 14 } })
521
+ }
522
+ )
523
+ ]
524
+ }
525
+ ),
526
+ /* @__PURE__ */ jsxs(Typography, { sx: { color: "text.secondary" }, children: [
527
+ "-",
528
+ formatAmount(discount.discount_amount || "0", discountCurrency.decimal),
529
+ " ",
530
+ discountCurrency.symbol
531
+ ] })
532
+ ]
533
+ }
534
+ ),
535
+ discountDescription && /* @__PURE__ */ jsx(
536
+ Typography,
537
+ {
538
+ sx: {
539
+ fontSize: "small",
540
+ color: notSupported ? "error.main" : "text.secondary",
541
+ mt: 0.5
542
+ },
543
+ children: discountDescription
544
+ }
545
+ )
546
+ ] }, discount.promotion_code || discount.coupon || `discount-${discount.discount_amount}`);
547
+ })
548
+ }
549
+ ),
373
550
  /* @__PURE__ */ jsxs(
374
551
  Stack,
375
552
  {
@@ -385,7 +562,7 @@ export default function PaymentSummary({
385
562
  t("common.total"),
386
563
  " "
387
564
  ] }),
388
- /* @__PURE__ */ jsx(PaymentAmount, { amount: `${totalAmount} ${currency.symbol}`, sx: { fontSize: "16px" } })
565
+ /* @__PURE__ */ jsx(PaymentAmount, { amount: `${totalAmount} ${discountCurrency.symbol}`, sx: { fontSize: "16px" } })
389
566
  ]
390
567
  }
391
568
  ),
@@ -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,155 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ module.exports = PromotionCode;
7
+ var _jsxRuntime = require("react/jsx-runtime");
8
+ var _context = require("@arcblock/ux/lib/Locale/context");
9
+ var _material = require("@mui/material");
10
+ var _iconsMaterial = require("@mui/icons-material");
11
+ var _react = require("react");
12
+ var _loadingButton = _interopRequireDefault(require("./loading-button"));
13
+ var _api = _interopRequireDefault(require("../libs/api"));
14
+ var _payment = require("../contexts/payment");
15
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
+ function PromotionCode({
17
+ checkoutSessionId,
18
+ initialAppliedCodes = [],
19
+ disabled = false,
20
+ className = "",
21
+ placeholder = "",
22
+ onUpdate = void 0,
23
+ currencyId
24
+ }) {
25
+ const {
26
+ t
27
+ } = (0, _context.useLocaleContext)();
28
+ const [showInput, setShowInput] = (0, _react.useState)(false);
29
+ const [code, setCode] = (0, _react.useState)("");
30
+ const [error, setError] = (0, _react.useState)("");
31
+ const [applying, setApplying] = (0, _react.useState)(false);
32
+ const [appliedCodes, setAppliedCodes] = (0, _react.useState)(initialAppliedCodes);
33
+ const {
34
+ session,
35
+ paymentState
36
+ } = (0, _payment.usePaymentContext)();
37
+ const handleLoginCheck = () => {
38
+ if (!session.user) {
39
+ session?.login(() => {
40
+ handleApply();
41
+ });
42
+ } else {
43
+ handleApply();
44
+ }
45
+ };
46
+ const handleApply = async () => {
47
+ if (!code.trim()) return;
48
+ if (paymentState.paying || paymentState.stripePaying) {
49
+ return;
50
+ }
51
+ setApplying(true);
52
+ setError("");
53
+ try {
54
+ const response = await _api.default.post(`/api/checkout-sessions/${checkoutSessionId}/apply-promotion`, {
55
+ promotion_code: code.trim(),
56
+ currency_id: currencyId
57
+ });
58
+ const discounts = response.data.discounts || [];
59
+ const appliedDiscount = discounts[0];
60
+ if (appliedDiscount) {
61
+ const newCode = {
62
+ id: appliedDiscount.promotion_code || appliedDiscount.coupon,
63
+ code: code.trim(),
64
+ discount_amount: appliedDiscount.discount_amount
65
+ };
66
+ setAppliedCodes([newCode]);
67
+ setCode("");
68
+ setShowInput(false);
69
+ onUpdate?.({
70
+ appliedCodes: [newCode],
71
+ discountAmount: appliedDiscount.discount_amount
72
+ });
73
+ }
74
+ } catch (err) {
75
+ const errorMessage = err.response?.data?.error || err.message;
76
+ setError(errorMessage);
77
+ } finally {
78
+ setApplying(false);
79
+ }
80
+ };
81
+ const handleKeyPress = event => {
82
+ if (event.key === "Enter" && !applying && code.trim()) {
83
+ handleLoginCheck();
84
+ }
85
+ };
86
+ const isPaymentInProgress = paymentState.paying || paymentState.stripePaying;
87
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
88
+ className,
89
+ children: appliedCodes.length === 0 && !disabled && !isPaymentInProgress && (showInput ? /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
90
+ onBlur: () => {
91
+ if (!code.trim()) {
92
+ setShowInput(false);
93
+ }
94
+ },
95
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.TextField, {
96
+ fullWidth: true,
97
+ value: code,
98
+ onChange: e => setCode(e.target.value),
99
+ onKeyPress: handleKeyPress,
100
+ placeholder: placeholder || t("payment.checkout.promotion.placeholder"),
101
+ variant: "outlined",
102
+ size: "small",
103
+ disabled: applying,
104
+ autoFocus: true,
105
+ slotProps: {
106
+ input: {
107
+ endAdornment: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.InputAdornment, {
108
+ position: "end",
109
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_loadingButton.default, {
110
+ size: "small",
111
+ onClick: handleLoginCheck,
112
+ loading: applying,
113
+ disabled: !code.trim(),
114
+ variant: "text",
115
+ sx: {
116
+ color: "primary.main",
117
+ fontSize: "small"
118
+ },
119
+ children: t("payment.checkout.promotion.apply")
120
+ })
121
+ })
122
+ }
123
+ },
124
+ sx: {
125
+ "& .MuiOutlinedInput-root": {
126
+ pr: 1
127
+ }
128
+ }
129
+ }), error && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Alert, {
130
+ severity: "error",
131
+ sx: {
132
+ my: 1
133
+ },
134
+ children: error
135
+ })]
136
+ }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
137
+ onClick: () => setShowInput(true),
138
+ startIcon: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.Add, {
139
+ fontSize: "small"
140
+ }),
141
+ variant: "text",
142
+ sx: {
143
+ fontWeight: "normal",
144
+ textTransform: "none",
145
+ justifyContent: "flex-start",
146
+ p: 0,
147
+ "&:hover": {
148
+ backgroundColor: "transparent",
149
+ textDecoration: "underline"
150
+ }
151
+ },
152
+ children: t("payment.checkout.promotion.add_code")
153
+ }))
154
+ });
155
+ }
@@ -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'];
@@ -145,6 +145,16 @@ function PaymentProvider({
145
145
  }, [session?.user]);
146
146
  const prefix = (0, _util.getPrefix)();
147
147
  const [payable, setPayable] = (0, _react.useState)(true);
148
+ const [paymentState, setPaymentState] = (0, _react.useState)({
149
+ paying: false,
150
+ stripePaying: false
151
+ });
152
+ const updatePaymentState = state => {
153
+ setPaymentState(prev => ({
154
+ ...prev,
155
+ ...state
156
+ }));
157
+ };
148
158
  if (error) {
149
159
  return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Alert, {
150
160
  severity: "error",
@@ -167,7 +177,9 @@ function PaymentProvider({
167
177
  setLivemode,
168
178
  api: _api.default,
169
179
  payable,
170
- setPayable
180
+ setPayable,
181
+ paymentState,
182
+ setPaymentState: updatePaymentState
171
183
  },
172
184
  children
173
185
  });
package/lib/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/lib/index.js CHANGED
@@ -47,6 +47,7 @@ var _exportNames = {
47
47
  AutoTopupModal: true,
48
48
  AutoTopup: true,
49
49
  Collapse: true,
50
+ PromotionCode: true,
50
51
  PaymentThemeProvider: true,
51
52
  translations: true,
52
53
  createTranslator: true
@@ -243,6 +244,12 @@ Object.defineProperty(exports, "ProductSkeleton", {
243
244
  return _productSkeleton.default;
244
245
  }
245
246
  });
247
+ Object.defineProperty(exports, "PromotionCode", {
248
+ enumerable: true,
249
+ get: function () {
250
+ return _promotionCode.default;
251
+ }
252
+ });
246
253
  Object.defineProperty(exports, "ResumeSubscription", {
247
254
  enumerable: true,
248
255
  get: function () {
@@ -368,6 +375,7 @@ var _dateRangePicker = _interopRequireDefault(require("./components/date-range-p
368
375
  var _modal = _interopRequireDefault(require("./components/auto-topup/modal"));
369
376
  var _autoTopup = _interopRequireDefault(require("./components/auto-topup"));
370
377
  var _collapse = _interopRequireDefault(require("./components/collapse"));
378
+ var _promotionCode = _interopRequireDefault(require("./components/promotion-code"));
371
379
  var _theme = require("./theme");
372
380
  var _util = require("./libs/util");
373
381
  Object.keys(_util).forEach(function (key) {
@@ -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/lib/libs/util.js CHANGED
@@ -10,6 +10,7 @@ exports.formatAmount = formatAmount;
10
10
  exports.formatAmountPrecisionLimit = formatAmountPrecisionLimit;
11
11
  exports.formatBNStr = formatBNStr;
12
12
  exports.formatCheckoutHeadlines = formatCheckoutHeadlines;
13
+ exports.formatCouponTerms = void 0;
13
14
  exports.formatDateTime = formatDateTime;
14
15
  exports.formatError = void 0;
15
16
  exports.formatLineItemPricing = formatLineItemPricing;
@@ -77,6 +78,34 @@ var _locales = require("../locales");
77
78
  var _dayjs = _interopRequireDefault(require("./dayjs"));
78
79
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
79
80
  const PAYMENT_KIT_DID = exports.PAYMENT_KIT_DID = "z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk";
81
+ const formatCouponTerms = (coupon, currency, locale = "en") => {
82
+ let couponOff = "";
83
+ if (coupon.percent_off && coupon.percent_off > 0) {
84
+ couponOff = (0, _locales.t)("payment.checkout.coupon.percentage", locale, {
85
+ percent: coupon.percent_off
86
+ });
87
+ }
88
+ if (coupon.amount_off && coupon.amount_off !== "0") {
89
+ const {
90
+ symbol
91
+ } = currency;
92
+ couponOff = coupon.currency_id === currency.id ? coupon.amount_off || "" : coupon.currency_options?.[currency.id]?.amount_off || "";
93
+ if (couponOff) {
94
+ couponOff = (0, _locales.t)("payment.checkout.coupon.fixedAmount", locale, {
95
+ amount: formatAmount(couponOff, currency.decimal),
96
+ symbol
97
+ });
98
+ }
99
+ }
100
+ if (!couponOff) {
101
+ return (0, _locales.t)("payment.checkout.coupon.noDiscount");
102
+ }
103
+ return (0, _locales.t)(`payment.checkout.coupon.terms.${coupon.duration}`, locale, {
104
+ couponOff,
105
+ months: coupon.duration_in_months || 0
106
+ });
107
+ };
108
+ exports.formatCouponTerms = formatCouponTerms;
80
109
  const isPaymentKitMounted = () => {
81
110
  return (window.blocklet?.componentMountPoints || []).some(x => x.did === PAYMENT_KIT_DID);
82
111
  };
package/lib/locales/en.js CHANGED
@@ -245,6 +245,31 @@ module.exports = (0, _flat.default)({
245
245
  orderSummary: "Order Summary",
246
246
  paymentDetails: "Payment Details",
247
247
  productListTotal: "Includes {total} items",
248
+ promotion: {
249
+ add_code: "Add promotion code",
250
+ enter_code: "Enter promotion code",
251
+ placeholder: "Enter code",
252
+ apply: "Apply",
253
+ applied: "Applied promotion codes",
254
+ dialog: {
255
+ title: "Add promotion code"
256
+ },
257
+ error: {
258
+ unknown: "Unknown error",
259
+ network: "Network error occurred",
260
+ removal: "Failed to remove code"
261
+ }
262
+ },
263
+ coupon: {
264
+ noDiscount: "No discount",
265
+ percentage: "{percent}% off",
266
+ fixedAmount: "{amount} {symbol} off",
267
+ terms: {
268
+ forever: "{couponOff} forever",
269
+ once: "{couponOff} once",
270
+ repeating: "{couponOff} for {months} month{months > 1 ? 's' : ''}"
271
+ }
272
+ },
248
273
  connectModal: {
249
274
  title: "{action}",
250
275
  scan: "Use the following methods to complete this payment",
package/lib/locales/zh.js CHANGED
@@ -233,6 +233,35 @@ module.exports = (0, _flat.default)({
233
233
  add: "\u6DFB\u52A0\u5230\u8BA2\u5355",
234
234
  remove: "\u4ECE\u8BA2\u5355\u79FB\u9664"
235
235
  },
236
+ promotion: {
237
+ add_code: "\u6DFB\u52A0\u4FC3\u9500\u7801",
238
+ enter_code: "\u8F93\u5165\u4FC3\u9500\u7801",
239
+ apply: "\u5E94\u7528",
240
+ applied: "\u5DF2\u5E94\u7528\u7684\u4FC3\u9500\u7801",
241
+ placeholder: "\u8F93\u5165\u4FC3\u9500\u7801",
242
+ duration_once: "1\u6B21\u4F18\u60E0 {amount} {symbol}",
243
+ duration_repeating: "{months}\u4E2A\u6708\u4F18\u60E0 {amount} {symbol}",
244
+ duration_forever: "\u6C38\u4E45\u4F18\u60E0 {amount} {symbol}",
245
+ dialog: {
246
+ title: "\u6DFB\u52A0\u4FC3\u9500\u7801"
247
+ },
248
+ error: {
249
+ invalid: "\u65E0\u6548\u7684\u4FC3\u9500\u7801",
250
+ expired: "\u4FC3\u9500\u7801\u5DF2\u8FC7\u671F",
251
+ used: "\u4FC3\u9500\u7801\u5DF2\u88AB\u4F7F\u7528",
252
+ not_applicable: "\u4FC3\u9500\u7801\u4E0D\u9002\u7528\u4E8E\u6B64\u8BA2\u5355"
253
+ }
254
+ },
255
+ coupon: {
256
+ noDiscount: "\u65E0\u4F18\u60E0",
257
+ percentage: "{percent}%",
258
+ fixedAmount: "{amount} {symbol}",
259
+ terms: {
260
+ forever: "\u6C38\u4E45\u4EAB {couponOff} \u6298\u6263",
261
+ once: "\u5355\u6B21\u4EAB {couponOff} \u6298\u6263",
262
+ repeating: "{months} \u4E2A\u6708\u5185\u4EAB {couponOff} \u6298\u6263"
263
+ }
264
+ },
236
265
  credit: {
237
266
  oneTimeInfo: "\u4ED8\u6B3E\u5B8C\u6210\u540E\u60A8\u5C06\u83B7\u5F97 {amount} {symbol} \u989D\u5EA6",
238
267
  recurringInfo: "\u60A8\u5C06{period}\u83B7\u5F97 {amount} {symbol} \u989D\u5EA6",