payment-kit 1.24.4 → 1.25.0
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/api/src/index.ts +3 -0
- package/api/src/libs/credit-utils.ts +21 -0
- package/api/src/libs/discount/discount.ts +13 -0
- package/api/src/libs/env.ts +5 -0
- package/api/src/libs/error.ts +14 -0
- package/api/src/libs/exchange-rate/coingecko-provider.ts +193 -0
- package/api/src/libs/exchange-rate/coinmarketcap-provider.ts +180 -0
- package/api/src/libs/exchange-rate/index.ts +5 -0
- package/api/src/libs/exchange-rate/service.ts +583 -0
- package/api/src/libs/exchange-rate/token-address-mapping.ts +84 -0
- package/api/src/libs/exchange-rate/token-addresses.json +2147 -0
- package/api/src/libs/exchange-rate/token-data-provider.ts +142 -0
- package/api/src/libs/exchange-rate/types.ts +114 -0
- package/api/src/libs/exchange-rate/validator.ts +319 -0
- package/api/src/libs/invoice-quote.ts +158 -0
- package/api/src/libs/invoice.ts +143 -7
- package/api/src/libs/math-utils.ts +46 -0
- package/api/src/libs/notification/template/billing-discrepancy.ts +3 -4
- package/api/src/libs/notification/template/customer-auto-recharge-failed.ts +174 -79
- package/api/src/libs/notification/template/customer-credit-grant-granted.ts +2 -3
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +3 -3
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +3 -3
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +2 -3
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +9 -4
- package/api/src/libs/notification/template/exchange-rate-alert.ts +202 -0
- package/api/src/libs/notification/template/subscription-slippage-exceeded.ts +203 -0
- package/api/src/libs/notification/template/subscription-slippage-warning.ts +212 -0
- package/api/src/libs/notification/template/subscription-will-canceled.ts +2 -2
- package/api/src/libs/notification/template/subscription-will-renew.ts +22 -8
- package/api/src/libs/payment.ts +1 -1
- package/api/src/libs/price.ts +4 -1
- package/api/src/libs/queue/index.ts +8 -0
- package/api/src/libs/quote-service.ts +1132 -0
- package/api/src/libs/quote-validation.ts +388 -0
- package/api/src/libs/session.ts +686 -39
- package/api/src/libs/slippage.ts +135 -0
- package/api/src/libs/subscription.ts +185 -15
- package/api/src/libs/util.ts +64 -3
- package/api/src/locales/en.ts +50 -0
- package/api/src/locales/zh.ts +48 -0
- package/api/src/queues/auto-recharge.ts +295 -21
- package/api/src/queues/exchange-rate-health.ts +242 -0
- package/api/src/queues/invoice.ts +48 -1
- package/api/src/queues/notification.ts +167 -1
- package/api/src/queues/payment.ts +177 -7
- package/api/src/queues/subscription.ts +436 -6
- package/api/src/routes/auto-recharge-configs.ts +71 -6
- package/api/src/routes/checkout-sessions.ts +1730 -81
- package/api/src/routes/connect/auto-recharge-auth.ts +2 -0
- package/api/src/routes/connect/change-payer.ts +2 -0
- package/api/src/routes/connect/change-payment.ts +61 -8
- package/api/src/routes/connect/change-plan.ts +161 -17
- package/api/src/routes/connect/collect.ts +9 -6
- package/api/src/routes/connect/delegation.ts +1 -0
- package/api/src/routes/connect/pay.ts +157 -0
- package/api/src/routes/connect/setup.ts +32 -10
- package/api/src/routes/connect/shared.ts +159 -13
- package/api/src/routes/connect/subscribe.ts +32 -9
- package/api/src/routes/credit-grants.ts +99 -0
- package/api/src/routes/exchange-rate-providers.ts +248 -0
- package/api/src/routes/exchange-rates.ts +87 -0
- package/api/src/routes/index.ts +4 -0
- package/api/src/routes/invoices.ts +280 -2
- package/api/src/routes/payment-links.ts +13 -0
- package/api/src/routes/prices.ts +84 -2
- package/api/src/routes/subscriptions.ts +526 -15
- package/api/src/store/migrations/20251220-dynamic-pricing.ts +245 -0
- package/api/src/store/migrations/20251223-exchange-rate-provider-type.ts +28 -0
- package/api/src/store/migrations/20260110-add-quote-locked-at.ts +23 -0
- package/api/src/store/migrations/20260112-add-checkout-session-slippage-percent.ts +22 -0
- package/api/src/store/migrations/20260113-add-price-quote-slippage-fields.ts +45 -0
- package/api/src/store/migrations/20260116-subscription-slippage.ts +21 -0
- package/api/src/store/migrations/20260120-auto-recharge-slippage.ts +21 -0
- package/api/src/store/models/auto-recharge-config.ts +12 -0
- package/api/src/store/models/checkout-session.ts +7 -0
- package/api/src/store/models/exchange-rate-provider.ts +225 -0
- package/api/src/store/models/index.ts +6 -0
- package/api/src/store/models/payment-intent.ts +6 -0
- package/api/src/store/models/price-quote.ts +284 -0
- package/api/src/store/models/price.ts +53 -5
- package/api/src/store/models/subscription.ts +11 -0
- package/api/src/store/models/types.ts +61 -1
- package/api/tests/libs/change-payment-plan.spec.ts +282 -0
- package/api/tests/libs/exchange-rate-service.spec.ts +341 -0
- package/api/tests/libs/quote-service.spec.ts +199 -0
- package/api/tests/libs/session.spec.ts +464 -0
- package/api/tests/libs/slippage.spec.ts +109 -0
- package/api/tests/libs/token-data-provider.spec.ts +267 -0
- package/api/tests/models/exchange-rate-provider.spec.ts +121 -0
- package/api/tests/models/price-dynamic.spec.ts +100 -0
- package/api/tests/models/price-quote.spec.ts +112 -0
- package/api/tests/routes/exchange-rate-providers.spec.ts +215 -0
- package/api/tests/routes/subscription-slippage.spec.ts +254 -0
- package/blocklet.yml +1 -1
- package/package.json +7 -6
- package/src/components/customer/credit-overview.tsx +14 -0
- package/src/components/discount/discount-info.tsx +8 -2
- package/src/components/invoice/list.tsx +146 -16
- package/src/components/invoice/table.tsx +276 -71
- package/src/components/invoice-pdf/template.tsx +3 -7
- package/src/components/metadata/form.tsx +6 -8
- package/src/components/price/form.tsx +519 -149
- package/src/components/promotion/active-redemptions.tsx +5 -3
- package/src/components/quote/info.tsx +234 -0
- package/src/hooks/subscription.ts +132 -2
- package/src/locales/en.tsx +145 -0
- package/src/locales/zh.tsx +143 -1
- package/src/pages/admin/billing/invoices/detail.tsx +41 -4
- package/src/pages/admin/products/exchange-rate-providers/edit-dialog.tsx +354 -0
- package/src/pages/admin/products/exchange-rate-providers/index.tsx +363 -0
- package/src/pages/admin/products/index.tsx +12 -1
- package/src/pages/customer/invoice/detail.tsx +36 -12
- package/src/pages/customer/subscription/change-payment.tsx +65 -3
- package/src/pages/customer/subscription/change-plan.tsx +207 -38
- package/src/pages/customer/subscription/detail.tsx +599 -419
|
@@ -3,11 +3,11 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
|
3
3
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
4
4
|
import {
|
|
5
5
|
LoadingButton,
|
|
6
|
+
PaymentSummary,
|
|
6
7
|
PricingTable,
|
|
7
8
|
api,
|
|
8
9
|
formatBNStr,
|
|
9
10
|
formatError,
|
|
10
|
-
formatPrice,
|
|
11
11
|
formatTime,
|
|
12
12
|
usePaymentContext,
|
|
13
13
|
} from '@blocklet/payment-react';
|
|
@@ -18,11 +18,118 @@ import { useRequest, useSetState } from 'ahooks';
|
|
|
18
18
|
import { useRef } from 'react';
|
|
19
19
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
|
20
20
|
|
|
21
|
-
import
|
|
21
|
+
import BigNumber from 'bignumber.js';
|
|
22
22
|
import SectionHeader from '../../../components/section/header';
|
|
23
23
|
import SubscriptionDescription from '../../../components/subscription/description';
|
|
24
24
|
import { goBackOrFallback } from '../../../libs/util';
|
|
25
25
|
import { useArcsphere } from '../../../hooks/browser';
|
|
26
|
+
import { useSubscriptionExchangeRate } from '../../../hooks/subscription';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Calculate total amount for items using live exchange rate
|
|
30
|
+
* For dynamic pricing items, calculate: base_amount / rate
|
|
31
|
+
* For fixed pricing items, use unit_amount directly
|
|
32
|
+
*/
|
|
33
|
+
const calculateTotalWithLiveRate = (
|
|
34
|
+
items: TLineItemExpanded[],
|
|
35
|
+
liveRate: string | undefined,
|
|
36
|
+
currencyDecimal: number
|
|
37
|
+
): string | null => {
|
|
38
|
+
if (!items?.length) return null;
|
|
39
|
+
|
|
40
|
+
let total = new BigNumber(0);
|
|
41
|
+
const hasAnyDynamicPricing = items.some((item) => {
|
|
42
|
+
const price = (item.upsell_price || item.price) as any;
|
|
43
|
+
return price?.pricing_type === 'dynamic';
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// If there's dynamic pricing but no live rate, return null
|
|
47
|
+
if (hasAnyDynamicPricing && !liveRate) return null;
|
|
48
|
+
|
|
49
|
+
for (const item of items) {
|
|
50
|
+
const price = (item.upsell_price || item.price) as any;
|
|
51
|
+
const quantity = item.quantity || 1;
|
|
52
|
+
|
|
53
|
+
if (price?.pricing_type === 'dynamic' && price?.base_amount && liveRate) {
|
|
54
|
+
// Dynamic pricing: amount = base_amount / rate * 10^decimal
|
|
55
|
+
const baseAmount = new BigNumber(price.base_amount);
|
|
56
|
+
const rate = new BigNumber(liveRate);
|
|
57
|
+
if (rate.gt(0)) {
|
|
58
|
+
const amount = baseAmount.dividedBy(rate).times(new BigNumber(10).pow(currencyDecimal)).times(quantity);
|
|
59
|
+
// Round up to ensure we don't undercharge
|
|
60
|
+
total = total.plus(amount.integerValue(BigNumber.ROUND_CEIL));
|
|
61
|
+
}
|
|
62
|
+
} else if (price?.unit_amount) {
|
|
63
|
+
// Fixed pricing: use unit_amount directly
|
|
64
|
+
total = total.plus(new BigNumber(price.unit_amount).times(quantity));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return total.toString();
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Proration type with additional info from backend
|
|
73
|
+
*/
|
|
74
|
+
type ProrationItem = {
|
|
75
|
+
price_id: string;
|
|
76
|
+
amount: string;
|
|
77
|
+
quantity: number;
|
|
78
|
+
description: string;
|
|
79
|
+
pricing_type?: string;
|
|
80
|
+
base_amount_usd?: string | null;
|
|
81
|
+
proration_rate?: number;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Recalculate prorations using live exchange rate for dynamic pricing items
|
|
86
|
+
* For fixed pricing items, use the original amount from backend
|
|
87
|
+
*
|
|
88
|
+
* IMPORTANT: The recalculated amount is capped at the original payment amount.
|
|
89
|
+
* This prevents the case where token depreciation causes the refund to exceed
|
|
90
|
+
* what the user originally paid.
|
|
91
|
+
*/
|
|
92
|
+
const recalculateProrations = (
|
|
93
|
+
prorations: ProrationItem[],
|
|
94
|
+
liveRate: string | undefined,
|
|
95
|
+
currencyDecimal: number
|
|
96
|
+
): ProrationItem[] => {
|
|
97
|
+
if (!prorations?.length) return [];
|
|
98
|
+
|
|
99
|
+
return prorations.map((p) => {
|
|
100
|
+
// Only recalculate for dynamic pricing items with base_amount_usd
|
|
101
|
+
if (p.pricing_type === 'dynamic' && p.base_amount_usd && p.proration_rate && liveRate) {
|
|
102
|
+
const baseAmountUsd = new BigNumber(p.base_amount_usd);
|
|
103
|
+
const rate = new BigNumber(liveRate);
|
|
104
|
+
const prorationRate = new BigNumber(p.proration_rate);
|
|
105
|
+
|
|
106
|
+
if (rate.gt(0)) {
|
|
107
|
+
// Calculate: base_amount_usd / rate * proration_rate * quantity * 10^decimal
|
|
108
|
+
const calculatedAmount = baseAmountUsd
|
|
109
|
+
.dividedBy(rate)
|
|
110
|
+
.times(prorationRate)
|
|
111
|
+
.times(p.quantity || 1)
|
|
112
|
+
.times(new BigNumber(10).pow(currencyDecimal))
|
|
113
|
+
.integerValue(BigNumber.ROUND_CEIL);
|
|
114
|
+
|
|
115
|
+
// Get the original amount from backend (absolute value)
|
|
116
|
+
const originalAmount = new BigNumber(p.amount.replace('-', ''));
|
|
117
|
+
|
|
118
|
+
// Cap at original amount: don't refund more than what was paid
|
|
119
|
+
// This handles the case where token depreciation would cause
|
|
120
|
+
// the calculated refund to exceed the original payment
|
|
121
|
+
const finalAmount = BigNumber.min(calculatedAmount, originalAmount);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
...p,
|
|
125
|
+
amount: `-${finalAmount.toString()}`, // Proration is negative (credit)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// For fixed pricing or if we can't recalculate, use original amount
|
|
130
|
+
return p;
|
|
131
|
+
});
|
|
132
|
+
};
|
|
26
133
|
|
|
27
134
|
const fetchData = async (
|
|
28
135
|
id: string
|
|
@@ -75,6 +182,25 @@ export default function CustomerSubscriptionChangePlan() {
|
|
|
75
182
|
paid: false,
|
|
76
183
|
});
|
|
77
184
|
|
|
185
|
+
let planItems: TLineItemExpanded[] = [];
|
|
186
|
+
if (state.items.length > 0) {
|
|
187
|
+
planItems = state.items as TLineItemExpanded[];
|
|
188
|
+
} else if (data?.subscription?.items) {
|
|
189
|
+
planItems = data.subscription.items as unknown as TLineItemExpanded[];
|
|
190
|
+
}
|
|
191
|
+
const planHasDynamicPricing =
|
|
192
|
+
planItems?.length &&
|
|
193
|
+
planItems.some((item) => {
|
|
194
|
+
const price = (item.upsell_price || item.price) as any;
|
|
195
|
+
return price?.pricing_type === 'dynamic';
|
|
196
|
+
});
|
|
197
|
+
const planNeedsExchangeRate = planHasDynamicPricing && data?.subscription?.paymentMethod?.type !== 'stripe';
|
|
198
|
+
const { liveRateInfo, liveRateUnavailable } = useSubscriptionExchangeRate({
|
|
199
|
+
subscriptionId: data?.subscription?.id,
|
|
200
|
+
currencyId: data?.subscription?.paymentCurrency?.id,
|
|
201
|
+
enabled: !!planNeedsExchangeRate,
|
|
202
|
+
});
|
|
203
|
+
|
|
78
204
|
if (error) {
|
|
79
205
|
return <Alert severity="error">{formatError(error)}</Alert>;
|
|
80
206
|
}
|
|
@@ -87,6 +213,18 @@ export default function CustomerSubscriptionChangePlan() {
|
|
|
87
213
|
return <Alert severity="error">{t('payment.customer.changePlan.subscriptionNotFound')}</Alert>;
|
|
88
214
|
}
|
|
89
215
|
|
|
216
|
+
const planItemsAfterData = state.items.length
|
|
217
|
+
? (state.items as TLineItemExpanded[])
|
|
218
|
+
: (data.subscription.items as unknown as TLineItemExpanded[]);
|
|
219
|
+
const planHasDynamicPricingAfterData =
|
|
220
|
+
planItemsAfterData?.length &&
|
|
221
|
+
planItemsAfterData.some((item) => {
|
|
222
|
+
const price = (item.upsell_price || item.price) as any;
|
|
223
|
+
return price?.pricing_type === 'dynamic';
|
|
224
|
+
});
|
|
225
|
+
const planNeedsExchangeRateAfterData =
|
|
226
|
+
planHasDynamicPricingAfterData && data.subscription.paymentMethod?.type !== 'stripe';
|
|
227
|
+
|
|
90
228
|
const handleSelect = async (priceId: string) => {
|
|
91
229
|
try {
|
|
92
230
|
if (state.priceId === priceId) {
|
|
@@ -219,6 +357,53 @@ export default function CustomerSubscriptionChangePlan() {
|
|
|
219
357
|
const { recurring } = data.subscription.items.find((x) => x.price.type === 'recurring')?.price || {};
|
|
220
358
|
const interval = [recurring?.interval, recurring?.interval_count].join('-');
|
|
221
359
|
|
|
360
|
+
// Calculate display values using live exchange rate for dynamic pricing
|
|
361
|
+
// For fixed pricing, use backend's values directly
|
|
362
|
+
const needsLiveRateCalculation = planNeedsExchangeRateAfterData && liveRateInfo?.rate;
|
|
363
|
+
|
|
364
|
+
// Recalculate prorations with current exchange rate for dynamic pricing items
|
|
365
|
+
const displayProrations = needsLiveRateCalculation
|
|
366
|
+
? recalculateProrations(
|
|
367
|
+
state.prorations as ProrationItem[],
|
|
368
|
+
liveRateInfo?.rate,
|
|
369
|
+
data.subscription.paymentCurrency.decimal
|
|
370
|
+
)
|
|
371
|
+
: state.prorations;
|
|
372
|
+
|
|
373
|
+
// Calculate total with current exchange rate
|
|
374
|
+
const calculatedTotal = needsLiveRateCalculation
|
|
375
|
+
? calculateTotalWithLiveRate(planItemsAfterData, liveRateInfo?.rate, data.subscription.paymentCurrency.decimal)
|
|
376
|
+
: null;
|
|
377
|
+
const displayTotal = calculatedTotal || state.total;
|
|
378
|
+
|
|
379
|
+
// Recalculate due and newCredit: total + sum(prorations) - appliedCredit
|
|
380
|
+
const calculateDisplayValues = (): { due: string; newCredit: string } => {
|
|
381
|
+
if (!needsLiveRateCalculation) {
|
|
382
|
+
return { due: state.due, newCredit: state.newCredit };
|
|
383
|
+
}
|
|
384
|
+
const totalBN = new BigNumber(displayTotal || '0');
|
|
385
|
+
const prorationsSum = displayProrations.reduce(
|
|
386
|
+
(sum: BigNumber, p: any) => sum.plus(new BigNumber(p.amount || '0')),
|
|
387
|
+
new BigNumber(0)
|
|
388
|
+
);
|
|
389
|
+
const appliedCreditBN = new BigNumber(state.appliedCredit || '0');
|
|
390
|
+
const balanceBN = totalBN.plus(prorationsSum).minus(appliedCreditBN);
|
|
391
|
+
|
|
392
|
+
// If balance is negative, due=0 and the excess becomes newCredit
|
|
393
|
+
// If balance is positive, due=balance and newCredit=0
|
|
394
|
+
if (balanceBN.lt(0)) {
|
|
395
|
+
return {
|
|
396
|
+
due: '0',
|
|
397
|
+
newCredit: balanceBN.abs().toString(),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
due: balanceBN.toString(),
|
|
402
|
+
newCredit: '0',
|
|
403
|
+
};
|
|
404
|
+
};
|
|
405
|
+
const { due: displayDue, newCredit: displayNewCredit } = calculateDisplayValues();
|
|
406
|
+
|
|
222
407
|
const getInfoRow = (label: string, value: string, prefix?: string) => {
|
|
223
408
|
return (
|
|
224
409
|
<Stack
|
|
@@ -232,7 +417,7 @@ export default function CustomerSubscriptionChangePlan() {
|
|
|
232
417
|
</Typography>
|
|
233
418
|
<Typography component="p" style={{ fontWeight: 'bold' }}>
|
|
234
419
|
{prefix}
|
|
235
|
-
{formatBNStr(value, data.subscription.paymentCurrency.decimal)} {data.subscription.paymentCurrency.symbol}
|
|
420
|
+
{formatBNStr(value, data.subscription.paymentCurrency.decimal, 2)} {data.subscription.paymentCurrency.symbol}
|
|
236
421
|
</Typography>
|
|
237
422
|
</Stack>
|
|
238
423
|
);
|
|
@@ -278,47 +463,31 @@ export default function CustomerSubscriptionChangePlan() {
|
|
|
278
463
|
{state.priceId && state.total && state.setup && (
|
|
279
464
|
<Stack direction="column" spacing={3} sx={{ maxWidth: 640 }}>
|
|
280
465
|
<SectionHeader title={t('payment.customer.changePlan.confirm')} />
|
|
281
|
-
<
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
{
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
key={x.price_id}
|
|
293
|
-
direction="row"
|
|
294
|
-
sx={{
|
|
295
|
-
alignItems: 'center',
|
|
296
|
-
justifyContent: 'space-between',
|
|
297
|
-
}}>
|
|
298
|
-
<InfoCard logo={product.images[0]} name={product.name} description={product.description} />
|
|
299
|
-
<Typography component="p" style={{ fontWeight: 'bold' }}>
|
|
300
|
-
{formatPrice(
|
|
301
|
-
x.price,
|
|
302
|
-
data.subscription.paymentCurrency,
|
|
303
|
-
x.price.product.unit_label,
|
|
304
|
-
1,
|
|
305
|
-
true,
|
|
306
|
-
locale
|
|
307
|
-
)}
|
|
308
|
-
</Typography>
|
|
309
|
-
</Stack>
|
|
310
|
-
);
|
|
466
|
+
<PaymentSummary
|
|
467
|
+
items={planItemsAfterData}
|
|
468
|
+
currency={data.subscription.paymentCurrency}
|
|
469
|
+
trialInDays={0}
|
|
470
|
+
billingThreshold={0}
|
|
471
|
+
showStaking={false}
|
|
472
|
+
liveRate={liveRateInfo}
|
|
473
|
+
rateUnavailable={!!(planNeedsExchangeRateAfterData && liveRateUnavailable)}
|
|
474
|
+
action={t('payment.customer.changePlan.summary', {
|
|
475
|
+
// @ts-ignore
|
|
476
|
+
date: formatTime(state.setup.period.end * 1000, 'lll', locale),
|
|
311
477
|
})}
|
|
312
|
-
|
|
478
|
+
completed
|
|
479
|
+
isStripePayment={data.subscription.paymentMethod?.type === 'stripe'}
|
|
480
|
+
/>
|
|
313
481
|
<Divider />
|
|
314
482
|
<Stack direction="column" spacing={1}>
|
|
315
|
-
{getInfoRow(t('payment.customer.changePlan.total'),
|
|
316
|
-
{
|
|
483
|
+
{getInfoRow(t('payment.customer.changePlan.total'), displayTotal)}
|
|
484
|
+
{displayProrations.map((x: any) => getInfoRow(x.description, x.amount))}
|
|
317
485
|
{state.appliedCredit > '0' &&
|
|
318
486
|
getInfoRow(t('payment.customer.changePlan.appliedCredit'), state.appliedCredit, '-')}
|
|
319
|
-
{
|
|
487
|
+
{new BigNumber(displayNewCredit).gt(0) &&
|
|
488
|
+
getInfoRow(t('payment.customer.changePlan.newCredit'), displayNewCredit)}
|
|
320
489
|
<Divider />
|
|
321
|
-
{getInfoRow(t('payment.customer.changePlan.remaining'),
|
|
490
|
+
{getInfoRow(t('payment.customer.changePlan.remaining'), displayDue)}
|
|
322
491
|
<Divider />
|
|
323
492
|
<Stack
|
|
324
493
|
direction="row"
|