@blocklet/payment-react 1.20.11 → 1.20.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/components/promotion-code.d.ts +19 -0
- package/es/components/promotion-code.js +153 -0
- package/es/contexts/payment.d.ts +8 -0
- package/es/contexts/payment.js +10 -1
- package/es/index.d.ts +2 -1
- package/es/index.js +3 -1
- package/es/libs/util.d.ts +5 -1
- package/es/libs/util.js +23 -0
- package/es/locales/en.js +25 -0
- package/es/locales/zh.js +29 -0
- package/es/payment/form/index.js +7 -1
- package/es/payment/index.js +19 -0
- package/es/payment/product-item.js +32 -3
- package/es/payment/summary.d.ts +5 -2
- package/es/payment/summary.js +193 -16
- package/lib/components/promotion-code.d.ts +19 -0
- package/lib/components/promotion-code.js +155 -0
- package/lib/contexts/payment.d.ts +8 -0
- package/lib/contexts/payment.js +13 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +8 -0
- package/lib/libs/util.d.ts +5 -1
- package/lib/libs/util.js +29 -0
- package/lib/locales/en.js +25 -0
- package/lib/locales/zh.js +29 -0
- package/lib/payment/form/index.js +8 -1
- package/lib/payment/index.js +23 -0
- package/lib/payment/product-item.js +46 -0
- package/lib/payment/summary.d.ts +5 -2
- package/lib/payment/summary.js +153 -11
- package/package.json +9 -9
- package/src/components/promotion-code.tsx +184 -0
- package/src/contexts/payment.tsx +15 -0
- package/src/index.ts +2 -0
- package/src/libs/util.ts +35 -0
- package/src/locales/en.tsx +25 -0
- package/src/locales/zh.tsx +29 -0
- package/src/payment/form/index.tsx +10 -1
- package/src/payment/index.tsx +22 -0
- package/src/payment/product-item.tsx +37 -2
- package/src/payment/summary.tsx +201 -16
package/src/libs/util.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
PaymentDetails,
|
|
6
6
|
PriceCurrency,
|
|
7
7
|
PriceRecurring,
|
|
8
|
+
TCoupon,
|
|
8
9
|
TInvoiceExpanded,
|
|
9
10
|
TLineItemExpanded,
|
|
10
11
|
TPaymentCurrency,
|
|
@@ -31,6 +32,40 @@ import type { ActionProps, PricingRenderProps } from '../types';
|
|
|
31
32
|
|
|
32
33
|
export const PAYMENT_KIT_DID = 'z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk';
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Format coupon discount terms for display
|
|
37
|
+
*/
|
|
38
|
+
export const formatCouponTerms = (coupon: TCoupon, currency: TPaymentCurrency, locale: string = 'en'): string => {
|
|
39
|
+
let couponOff = '';
|
|
40
|
+
|
|
41
|
+
if (coupon.percent_off && coupon.percent_off > 0) {
|
|
42
|
+
couponOff = t('payment.checkout.coupon.percentage', locale, { percent: coupon.percent_off });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (coupon.amount_off && coupon.amount_off !== '0') {
|
|
46
|
+
const { symbol } = currency;
|
|
47
|
+
couponOff =
|
|
48
|
+
coupon.currency_id === currency.id
|
|
49
|
+
? coupon.amount_off || ''
|
|
50
|
+
: coupon.currency_options?.[currency.id]?.amount_off || '';
|
|
51
|
+
if (couponOff) {
|
|
52
|
+
couponOff = t('payment.checkout.coupon.fixedAmount', locale, {
|
|
53
|
+
amount: formatAmount(couponOff, currency.decimal),
|
|
54
|
+
symbol,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!couponOff) {
|
|
60
|
+
return t('payment.checkout.coupon.noDiscount');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return t(`payment.checkout.coupon.terms.${coupon.duration}`, locale, {
|
|
64
|
+
couponOff,
|
|
65
|
+
months: coupon.duration_in_months || 0,
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
34
69
|
export const isPaymentKitMounted = () => {
|
|
35
70
|
return (window.blocklet?.componentMountPoints || []).some((x: any) => x.did === PAYMENT_KIT_DID);
|
|
36
71
|
};
|
package/src/locales/en.tsx
CHANGED
|
@@ -246,6 +246,31 @@ export default flat({
|
|
|
246
246
|
orderSummary: 'Order Summary',
|
|
247
247
|
paymentDetails: 'Payment Details',
|
|
248
248
|
productListTotal: 'Includes {total} items',
|
|
249
|
+
promotion: {
|
|
250
|
+
add_code: 'Add promotion code',
|
|
251
|
+
enter_code: 'Enter promotion code',
|
|
252
|
+
placeholder: 'Enter code',
|
|
253
|
+
apply: 'Apply',
|
|
254
|
+
applied: 'Applied promotion codes',
|
|
255
|
+
dialog: {
|
|
256
|
+
title: 'Add promotion code',
|
|
257
|
+
},
|
|
258
|
+
error: {
|
|
259
|
+
unknown: 'Unknown error',
|
|
260
|
+
network: 'Network error occurred',
|
|
261
|
+
removal: 'Failed to remove code',
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
coupon: {
|
|
265
|
+
noDiscount: 'No discount',
|
|
266
|
+
percentage: '{percent}% off',
|
|
267
|
+
fixedAmount: '{amount} {symbol} off',
|
|
268
|
+
terms: {
|
|
269
|
+
forever: '{couponOff} forever',
|
|
270
|
+
once: '{couponOff} once',
|
|
271
|
+
repeating: "{couponOff} for {months} month{months > 1 ? 's' : ''}",
|
|
272
|
+
},
|
|
273
|
+
},
|
|
249
274
|
connectModal: {
|
|
250
275
|
title: '{action}',
|
|
251
276
|
scan: 'Use the following methods to complete this payment',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -229,6 +229,35 @@ export default flat({
|
|
|
229
229
|
add: '添加到订单',
|
|
230
230
|
remove: '从订单移除',
|
|
231
231
|
},
|
|
232
|
+
promotion: {
|
|
233
|
+
add_code: '添加促销码',
|
|
234
|
+
enter_code: '输入促销码',
|
|
235
|
+
apply: '应用',
|
|
236
|
+
applied: '已应用的促销码',
|
|
237
|
+
placeholder: '输入促销码',
|
|
238
|
+
duration_once: '1次优惠 {amount} {symbol}',
|
|
239
|
+
duration_repeating: '{months}个月优惠 {amount} {symbol}',
|
|
240
|
+
duration_forever: '永久优惠 {amount} {symbol}',
|
|
241
|
+
dialog: {
|
|
242
|
+
title: '添加促销码',
|
|
243
|
+
},
|
|
244
|
+
error: {
|
|
245
|
+
invalid: '无效的促销码',
|
|
246
|
+
expired: '促销码已过期',
|
|
247
|
+
used: '促销码已被使用',
|
|
248
|
+
not_applicable: '促销码不适用于此订单',
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
coupon: {
|
|
252
|
+
noDiscount: '无优惠',
|
|
253
|
+
percentage: '{percent}%',
|
|
254
|
+
fixedAmount: '{amount} {symbol}',
|
|
255
|
+
terms: {
|
|
256
|
+
forever: '永久享 {couponOff} 折扣',
|
|
257
|
+
once: '单次享 {couponOff} 折扣',
|
|
258
|
+
repeating: '{months} 个月内享 {couponOff} 折扣',
|
|
259
|
+
},
|
|
260
|
+
},
|
|
232
261
|
credit: {
|
|
233
262
|
oneTimeInfo: '付款完成后您将获得 {amount} {symbol} 额度',
|
|
234
263
|
recurringInfo: '您将{period}获得 {amount} {symbol} 额度',
|
|
@@ -176,7 +176,7 @@ export default function PaymentForm({
|
|
|
176
176
|
// const theme = useTheme();
|
|
177
177
|
const { t, locale } = useLocaleContext();
|
|
178
178
|
const { isMobile } = useMobile();
|
|
179
|
-
const { session, connect, payable } = usePaymentContext();
|
|
179
|
+
const { session, connect, payable, setPaymentState } = usePaymentContext();
|
|
180
180
|
const subscription = useSubscription('events');
|
|
181
181
|
const formErrorPosition = 'bottom';
|
|
182
182
|
const {
|
|
@@ -244,6 +244,15 @@ export default function PaymentForm({
|
|
|
244
244
|
}
|
|
245
245
|
}, [subscription]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
246
246
|
|
|
247
|
+
// Sync payment states to PaymentContext
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
setPaymentState({
|
|
250
|
+
paying: state.submitting || state.paying,
|
|
251
|
+
stripePaying: state.stripePaying,
|
|
252
|
+
});
|
|
253
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
254
|
+
}, [state.submitting, state.paying, state.stripePaying]);
|
|
255
|
+
|
|
247
256
|
const mergeUserInfo = (
|
|
248
257
|
customerInfo: UserInfo | (TCustomer & { fullName?: string }),
|
|
249
258
|
userInfo?: UserInfo
|
package/src/payment/index.tsx
CHANGED
|
@@ -177,6 +177,15 @@ function PaymentInner({
|
|
|
177
177
|
if (onChange) {
|
|
178
178
|
onChange(methods.getValues());
|
|
179
179
|
}
|
|
180
|
+
if ((state.checkoutSession as any)?.discounts?.length) {
|
|
181
|
+
api
|
|
182
|
+
.post(`/api/checkout-sessions/${state.checkoutSession.id}/recalculate-promotion`, {
|
|
183
|
+
currency_id: currencyId,
|
|
184
|
+
})
|
|
185
|
+
.then(() => {
|
|
186
|
+
onPromotionUpdate();
|
|
187
|
+
});
|
|
188
|
+
}
|
|
180
189
|
}, [currencyId]); // eslint-disable-line
|
|
181
190
|
|
|
182
191
|
const onUpsell = async (from: string, to: string) => {
|
|
@@ -245,6 +254,16 @@ function PaymentInner({
|
|
|
245
254
|
}
|
|
246
255
|
};
|
|
247
256
|
|
|
257
|
+
const onPromotionUpdate = async () => {
|
|
258
|
+
try {
|
|
259
|
+
const { data } = await api.get(`/api/checkout-sessions/retrieve/${state.checkoutSession.id}`);
|
|
260
|
+
setState({ checkoutSession: data.checkoutSession });
|
|
261
|
+
} catch (err) {
|
|
262
|
+
console.error(err);
|
|
263
|
+
Toast.error(formatError(err));
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
248
267
|
const handlePaid = (result: any) => {
|
|
249
268
|
setState({ checkoutSession: result.checkoutSession });
|
|
250
269
|
onPaid(result);
|
|
@@ -292,6 +311,9 @@ function PaymentInner({
|
|
|
292
311
|
donationSettings={paymentLink?.donation_settings}
|
|
293
312
|
action={action}
|
|
294
313
|
completed={completed}
|
|
314
|
+
checkoutSession={state.checkoutSession}
|
|
315
|
+
onPromotionUpdate={onPromotionUpdate}
|
|
316
|
+
paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
|
|
295
317
|
showFeatures={showFeatures}
|
|
296
318
|
/>
|
|
297
319
|
{mode === 'standalone' && !isMobile && (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
2
|
import type { PriceRecurring, TLineItemExpanded, TPaymentCurrency } from '@blocklet/payment-types';
|
|
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
|
|
|
6
6
|
import React, { useMemo, useState } from 'react';
|
|
7
7
|
import Status from '../components/status';
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
formatQuantityInventory,
|
|
15
15
|
formatRecurring,
|
|
16
16
|
formatUpsellSaving,
|
|
17
|
+
formatAmount,
|
|
17
18
|
} from '../libs/util';
|
|
18
19
|
import ProductCard from './product-card';
|
|
19
20
|
import dayjs from '../libs/dayjs';
|
|
@@ -212,6 +213,40 @@ export default function ProductItem({
|
|
|
212
213
|
)}
|
|
213
214
|
</Stack>
|
|
214
215
|
</Stack>
|
|
216
|
+
|
|
217
|
+
{/* Display discount information for this item */}
|
|
218
|
+
{item.discount_amounts && item.discount_amounts.length > 0 && (
|
|
219
|
+
<Stack direction="row" spacing={1} sx={{ mt: 1, alignItems: 'center' }}>
|
|
220
|
+
{item.discount_amounts.map((discountAmount: any) => (
|
|
221
|
+
<Chip
|
|
222
|
+
key={discountAmount.promotion_code}
|
|
223
|
+
icon={<LocalOffer sx={{ fontSize: '0.8rem !important' }} />}
|
|
224
|
+
label={
|
|
225
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
226
|
+
<Typography component="span" sx={{ fontSize: '0.75rem', fontWeight: 'medium' }}>
|
|
227
|
+
{discountAmount.promotion_code?.code || 'DISCOUNT'}
|
|
228
|
+
</Typography>
|
|
229
|
+
<Typography component="span" sx={{ fontSize: '0.75rem' }}>
|
|
230
|
+
(-{formatAmount(discountAmount.amount || '0', currency.decimal)} {currency.symbol})
|
|
231
|
+
</Typography>
|
|
232
|
+
</Box>
|
|
233
|
+
}
|
|
234
|
+
size="small"
|
|
235
|
+
variant="filled"
|
|
236
|
+
sx={{
|
|
237
|
+
height: 20,
|
|
238
|
+
'& .MuiChip-icon': {
|
|
239
|
+
color: 'warning.main',
|
|
240
|
+
},
|
|
241
|
+
'& .MuiChip-label': {
|
|
242
|
+
px: 1,
|
|
243
|
+
},
|
|
244
|
+
}}
|
|
245
|
+
/>
|
|
246
|
+
))}
|
|
247
|
+
</Stack>
|
|
248
|
+
)}
|
|
249
|
+
|
|
215
250
|
{showFeatures && features.length > 0 && (
|
|
216
251
|
<Box
|
|
217
252
|
sx={{
|
package/src/payment/summary.tsx
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/indent */
|
|
1
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import type {
|
|
4
|
+
DonationSettings,
|
|
5
|
+
TLineItemExpanded,
|
|
6
|
+
TPaymentCurrency,
|
|
7
|
+
TCheckoutSession,
|
|
8
|
+
TPaymentMethodExpanded,
|
|
9
|
+
} from '@blocklet/payment-types';
|
|
10
|
+
import { HelpOutline, Close, LocalOffer } from '@mui/icons-material';
|
|
11
|
+
import { Box, Divider, Fade, Grow, Stack, Tooltip, Typography, Collapse, IconButton, Button } from '@mui/material';
|
|
5
12
|
import type { IconButtonProps } from '@mui/material';
|
|
6
13
|
import { BN, fromTokenToUnit, fromUnitToToken } from '@ocap/util';
|
|
7
14
|
import { useRequest, useSetState } from 'ahooks';
|
|
@@ -11,7 +18,14 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
|
11
18
|
import { styled } from '@mui/material/styles';
|
|
12
19
|
import Status from '../components/status';
|
|
13
20
|
import api from '../libs/api';
|
|
14
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
formatAmount,
|
|
23
|
+
formatCheckoutHeadlines,
|
|
24
|
+
getPriceUintAmountByCurrency,
|
|
25
|
+
formatCouponTerms,
|
|
26
|
+
formatNumber,
|
|
27
|
+
findCurrency,
|
|
28
|
+
} from '../libs/util';
|
|
15
29
|
import PaymentAmount from './amount';
|
|
16
30
|
import ProductDonation from './product-donation';
|
|
17
31
|
import ProductItem from './product-item';
|
|
@@ -19,6 +33,7 @@ import Livemode from '../components/livemode';
|
|
|
19
33
|
import { usePaymentContext } from '../contexts/payment';
|
|
20
34
|
import { useMobile } from '../hooks/mobile';
|
|
21
35
|
import LoadingButton from '../components/loading-button';
|
|
36
|
+
import PromotionCode from '../components/promotion-code';
|
|
22
37
|
|
|
23
38
|
// const shake = keyframes`
|
|
24
39
|
// 0% {
|
|
@@ -71,6 +86,9 @@ type Props = {
|
|
|
71
86
|
donationSettings?: DonationSettings; // only include backend part
|
|
72
87
|
action?: string;
|
|
73
88
|
completed?: boolean;
|
|
89
|
+
checkoutSession?: TCheckoutSession;
|
|
90
|
+
onPromotionUpdate?: () => void;
|
|
91
|
+
paymentMethods?: TPaymentMethodExpanded[];
|
|
74
92
|
showFeatures?: boolean;
|
|
75
93
|
};
|
|
76
94
|
|
|
@@ -139,23 +157,74 @@ export default function PaymentSummary({
|
|
|
139
157
|
action = '',
|
|
140
158
|
trialEnd = 0,
|
|
141
159
|
completed = false,
|
|
160
|
+
checkoutSession = undefined,
|
|
161
|
+
paymentMethods = [],
|
|
162
|
+
onPromotionUpdate = noop,
|
|
142
163
|
showFeatures = false,
|
|
143
164
|
...rest
|
|
144
165
|
}: Props) {
|
|
145
166
|
const { t, locale } = useLocaleContext();
|
|
146
167
|
const { isMobile } = useMobile();
|
|
147
|
-
const settings = usePaymentContext();
|
|
168
|
+
const { paymentState, ...settings } = usePaymentContext();
|
|
148
169
|
const [state, setState] = useSetState({ loading: false, shake: false, expanded: items?.length < 3 });
|
|
149
170
|
const { data, runAsync } = useRequest(() =>
|
|
150
171
|
checkoutSessionId ? fetchCrossSell(checkoutSessionId) : Promise.resolve(null)
|
|
151
172
|
);
|
|
152
|
-
|
|
153
|
-
const
|
|
173
|
+
|
|
174
|
+
const sessionDiscounts = (checkoutSession as any)?.discounts || [];
|
|
175
|
+
const allowPromotionCodes = !!checkoutSession?.allow_promotion_codes;
|
|
176
|
+
const hasDiscounts = sessionDiscounts?.length > 0;
|
|
177
|
+
|
|
178
|
+
const discountCurrency =
|
|
179
|
+
paymentMethods && checkoutSession
|
|
180
|
+
? (findCurrency(
|
|
181
|
+
paymentMethods as TPaymentMethodExpanded[],
|
|
182
|
+
hasDiscounts ? checkoutSession?.currency_id || currency.id : (currency.id as string)
|
|
183
|
+
) as TPaymentCurrency) || settings.settings?.baseCurrency
|
|
184
|
+
: currency;
|
|
185
|
+
|
|
186
|
+
const headlines = formatCheckoutHeadlines(items, discountCurrency, { trialEnd, trialInDays }, locale);
|
|
187
|
+
const staking = showStaking ? getStakingSetup(items, discountCurrency, billingThreshold) : '0';
|
|
188
|
+
|
|
189
|
+
const getAppliedPromotionCodes = () => {
|
|
190
|
+
if (!sessionDiscounts?.length) return [];
|
|
191
|
+
|
|
192
|
+
return sessionDiscounts.map((discount: any) => ({
|
|
193
|
+
id: discount.promotion_code || discount.coupon,
|
|
194
|
+
code: discount.verification_data?.code || 'APPLIED',
|
|
195
|
+
discount_amount: discount.discount_amount,
|
|
196
|
+
}));
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const handlePromotionUpdate = () => {
|
|
200
|
+
onPromotionUpdate?.();
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const handleRemovePromotion = async (sessionId: string) => {
|
|
204
|
+
// Prevent removing promotion during payment process
|
|
205
|
+
if (paymentState.paying || paymentState.stripePaying) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
await api.delete(`/api/checkout-sessions/${sessionId}/remove-promotion`);
|
|
210
|
+
onPromotionUpdate?.();
|
|
211
|
+
} catch (err: any) {
|
|
212
|
+
console.error('Failed to remove promotion code:', err);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const discountAmount = new BN(checkoutSession?.total_details?.amount_discount || '0');
|
|
217
|
+
|
|
218
|
+
const subtotalAmount = fromUnitToToken(
|
|
219
|
+
new BN(fromTokenToUnit(headlines.actualAmount, discountCurrency?.decimal)).add(new BN(staking)).toString(),
|
|
220
|
+
discountCurrency?.decimal
|
|
221
|
+
);
|
|
154
222
|
|
|
155
223
|
const totalAmount = fromUnitToToken(
|
|
156
|
-
new BN(fromTokenToUnit(
|
|
157
|
-
|
|
224
|
+
new BN(fromTokenToUnit(subtotalAmount, discountCurrency?.decimal)).sub(discountAmount).toString(),
|
|
225
|
+
discountCurrency?.decimal
|
|
158
226
|
);
|
|
227
|
+
|
|
159
228
|
useBus(
|
|
160
229
|
'error.REQUIRE_CROSS_SELL',
|
|
161
230
|
() => {
|
|
@@ -217,20 +286,20 @@ export default function PaymentSummary({
|
|
|
217
286
|
{items.map((x: TLineItemExpanded) =>
|
|
218
287
|
x.price.custom_unit_amount && onChangeAmount && donationSettings ? (
|
|
219
288
|
<ProductDonation
|
|
220
|
-
key={`${x.price_id}-${
|
|
289
|
+
key={`${x.price_id}-${discountCurrency.id}`}
|
|
221
290
|
item={x}
|
|
222
291
|
settings={donationSettings}
|
|
223
292
|
onChange={onChangeAmount}
|
|
224
|
-
currency={
|
|
293
|
+
currency={discountCurrency}
|
|
225
294
|
/>
|
|
226
295
|
) : (
|
|
227
296
|
<ProductItem
|
|
228
|
-
key={`${x.price_id}-${
|
|
297
|
+
key={`${x.price_id}-${discountCurrency.id}`}
|
|
229
298
|
item={x}
|
|
230
299
|
items={items}
|
|
231
300
|
trialInDays={trialInDays}
|
|
232
301
|
trialEnd={trialEnd}
|
|
233
|
-
currency={
|
|
302
|
+
currency={discountCurrency}
|
|
234
303
|
onUpsell={handleUpsell}
|
|
235
304
|
onDownsell={handleDownsell}
|
|
236
305
|
adjustableQuantity={x.adjustable_quantity}
|
|
@@ -272,7 +341,7 @@ export default function PaymentSummary({
|
|
|
272
341
|
item={{ quantity: 1, price: data, price_id: data.id, cross_sell: true } as TLineItemExpanded}
|
|
273
342
|
items={items}
|
|
274
343
|
trialInDays={trialInDays}
|
|
275
|
-
currency={
|
|
344
|
+
currency={discountCurrency}
|
|
276
345
|
trialEnd={trialEnd}
|
|
277
346
|
onUpsell={noop}
|
|
278
347
|
onDownsell={noop}>
|
|
@@ -393,11 +462,127 @@ export default function PaymentSummary({
|
|
|
393
462
|
</Tooltip>
|
|
394
463
|
</Stack>
|
|
395
464
|
<Typography>
|
|
396
|
-
{formatAmount(staking,
|
|
465
|
+
{formatAmount(staking, discountCurrency.decimal)} {discountCurrency.symbol}
|
|
397
466
|
</Typography>
|
|
398
467
|
</Stack>
|
|
399
468
|
</>
|
|
400
469
|
)}
|
|
470
|
+
{(allowPromotionCodes || hasDiscounts) && (
|
|
471
|
+
<Stack
|
|
472
|
+
direction="row"
|
|
473
|
+
spacing={1}
|
|
474
|
+
sx={{
|
|
475
|
+
justifyContent: 'space-between',
|
|
476
|
+
alignItems: 'center',
|
|
477
|
+
...(staking > 0 && {
|
|
478
|
+
borderTop: '1px solid',
|
|
479
|
+
borderColor: 'divider',
|
|
480
|
+
pt: 1,
|
|
481
|
+
mt: 1,
|
|
482
|
+
}),
|
|
483
|
+
}}>
|
|
484
|
+
<Typography className="base-label">{t('common.subtotal')}</Typography>
|
|
485
|
+
<Typography>
|
|
486
|
+
{formatNumber(subtotalAmount)} {discountCurrency.symbol}
|
|
487
|
+
</Typography>
|
|
488
|
+
</Stack>
|
|
489
|
+
)}
|
|
490
|
+
{/* Promotion Code Section - only show add button if no discounts applied */}
|
|
491
|
+
{allowPromotionCodes && !hasDiscounts && (
|
|
492
|
+
<Box sx={{ mt: 1 }}>
|
|
493
|
+
<PromotionCode
|
|
494
|
+
checkoutSessionId={checkoutSession.id}
|
|
495
|
+
initialAppliedCodes={getAppliedPromotionCodes()}
|
|
496
|
+
disabled={completed}
|
|
497
|
+
onUpdate={handlePromotionUpdate}
|
|
498
|
+
currencyId={currency.id}
|
|
499
|
+
/>
|
|
500
|
+
</Box>
|
|
501
|
+
)}
|
|
502
|
+
|
|
503
|
+
{/* Promotion Code Details */}
|
|
504
|
+
{hasDiscounts && (
|
|
505
|
+
<Box
|
|
506
|
+
sx={{
|
|
507
|
+
py: 1.5,
|
|
508
|
+
}}>
|
|
509
|
+
{sessionDiscounts.map((discount: any) => {
|
|
510
|
+
const promotionCodeInfo = discount.promotion_code_details;
|
|
511
|
+
const couponInfo = discount.coupon_details;
|
|
512
|
+
const discountDescription = couponInfo ? formatCouponTerms(couponInfo, discountCurrency, locale) : '';
|
|
513
|
+
const notSupported = discountDescription === t('payment.checkout.coupon.noDiscount');
|
|
514
|
+
|
|
515
|
+
return (
|
|
516
|
+
<Stack key={discount.promotion_code || discount.coupon || `discount-${discount.discount_amount}`}>
|
|
517
|
+
<Stack
|
|
518
|
+
direction="row"
|
|
519
|
+
spacing={1}
|
|
520
|
+
sx={{
|
|
521
|
+
justifyContent: 'space-between',
|
|
522
|
+
alignItems: 'center',
|
|
523
|
+
}}>
|
|
524
|
+
<Stack
|
|
525
|
+
direction="row"
|
|
526
|
+
spacing={1}
|
|
527
|
+
sx={{
|
|
528
|
+
alignItems: 'center',
|
|
529
|
+
backgroundColor: 'grey.100',
|
|
530
|
+
width: 'fit-content',
|
|
531
|
+
px: 1,
|
|
532
|
+
py: 0.5,
|
|
533
|
+
borderRadius: 1,
|
|
534
|
+
}}>
|
|
535
|
+
<Typography
|
|
536
|
+
sx={{
|
|
537
|
+
fontWeight: 'medium',
|
|
538
|
+
fontSize: 'small',
|
|
539
|
+
display: 'flex',
|
|
540
|
+
alignItems: 'center',
|
|
541
|
+
gap: 0.5,
|
|
542
|
+
}}>
|
|
543
|
+
<LocalOffer sx={{ color: 'warning.main', fontSize: 'small' }} />
|
|
544
|
+
{promotionCodeInfo?.code || discount.verification_data?.code || t('payment.checkout.discount')}
|
|
545
|
+
</Typography>
|
|
546
|
+
{!completed && (
|
|
547
|
+
<Button
|
|
548
|
+
size="small"
|
|
549
|
+
disabled={paymentState.paying || paymentState.stripePaying}
|
|
550
|
+
onClick={() => handleRemovePromotion(checkoutSessionId)}
|
|
551
|
+
sx={{
|
|
552
|
+
minWidth: 'auto',
|
|
553
|
+
width: 16,
|
|
554
|
+
height: 16,
|
|
555
|
+
color: 'text.secondary',
|
|
556
|
+
'&.Mui-disabled': {
|
|
557
|
+
color: 'text.disabled',
|
|
558
|
+
},
|
|
559
|
+
}}>
|
|
560
|
+
<Close sx={{ fontSize: 14 }} />
|
|
561
|
+
</Button>
|
|
562
|
+
)}
|
|
563
|
+
</Stack>
|
|
564
|
+
<Typography sx={{ color: 'text.secondary' }}>
|
|
565
|
+
-{formatAmount(discount.discount_amount || '0', discountCurrency.decimal)}{' '}
|
|
566
|
+
{discountCurrency.symbol}
|
|
567
|
+
</Typography>
|
|
568
|
+
</Stack>
|
|
569
|
+
{/* Show discount description */}
|
|
570
|
+
{discountDescription && (
|
|
571
|
+
<Typography
|
|
572
|
+
sx={{
|
|
573
|
+
fontSize: 'small',
|
|
574
|
+
color: notSupported ? 'error.main' : 'text.secondary',
|
|
575
|
+
mt: 0.5,
|
|
576
|
+
}}>
|
|
577
|
+
{discountDescription}
|
|
578
|
+
</Typography>
|
|
579
|
+
)}
|
|
580
|
+
</Stack>
|
|
581
|
+
);
|
|
582
|
+
})}
|
|
583
|
+
</Box>
|
|
584
|
+
)}
|
|
585
|
+
|
|
401
586
|
<Stack
|
|
402
587
|
sx={{
|
|
403
588
|
display: 'flex',
|
|
@@ -407,7 +592,7 @@ export default function PaymentSummary({
|
|
|
407
592
|
width: '100%',
|
|
408
593
|
}}>
|
|
409
594
|
<Box className="base-label">{t('common.total')} </Box>
|
|
410
|
-
<PaymentAmount amount={`${totalAmount} ${
|
|
595
|
+
<PaymentAmount amount={`${totalAmount} ${discountCurrency.symbol}`} sx={{ fontSize: '16px' }} />
|
|
411
596
|
</Stack>
|
|
412
597
|
{headlines.then && headlines.showThen && (
|
|
413
598
|
<Typography
|