payment-kit 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.
Files changed (92) hide show
  1. package/api/src/crons/index.ts +8 -0
  2. package/api/src/index.ts +2 -0
  3. package/api/src/integrations/stripe/handlers/invoice.ts +63 -5
  4. package/api/src/integrations/stripe/handlers/payment-intent.ts +1 -0
  5. package/api/src/integrations/stripe/resource.ts +253 -2
  6. package/api/src/libs/currency.ts +31 -0
  7. package/api/src/libs/discount/coupon.ts +1061 -0
  8. package/api/src/libs/discount/discount.ts +349 -0
  9. package/api/src/libs/discount/nft.ts +239 -0
  10. package/api/src/libs/discount/redemption.ts +636 -0
  11. package/api/src/libs/discount/vc.ts +73 -0
  12. package/api/src/libs/env.ts +1 -0
  13. package/api/src/libs/invoice.ts +44 -10
  14. package/api/src/libs/math-utils.ts +6 -0
  15. package/api/src/libs/price.ts +43 -0
  16. package/api/src/libs/session.ts +242 -57
  17. package/api/src/libs/subscription.ts +2 -6
  18. package/api/src/libs/vendor-util/adapters/launcher-adapter.ts +1 -1
  19. package/api/src/libs/vendor-util/adapters/types.ts +1 -0
  20. package/api/src/libs/vendor-util/fulfillment.ts +1 -1
  21. package/api/src/queues/auto-recharge.ts +1 -1
  22. package/api/src/queues/discount-status.ts +200 -0
  23. package/api/src/queues/subscription.ts +98 -5
  24. package/api/src/queues/usage-record.ts +1 -1
  25. package/api/src/queues/vendors/fulfillment-coordinator.ts +1 -29
  26. package/api/src/queues/vendors/return-processor.ts +184 -0
  27. package/api/src/queues/vendors/return-scanner.ts +119 -0
  28. package/api/src/queues/vendors/status-check.ts +1 -1
  29. package/api/src/routes/auto-recharge-configs.ts +5 -3
  30. package/api/src/routes/checkout-sessions.ts +755 -64
  31. package/api/src/routes/connect/change-payment.ts +6 -1
  32. package/api/src/routes/connect/change-plan.ts +6 -1
  33. package/api/src/routes/connect/setup.ts +6 -1
  34. package/api/src/routes/connect/shared.ts +80 -9
  35. package/api/src/routes/connect/subscribe.ts +12 -2
  36. package/api/src/routes/coupons.ts +518 -0
  37. package/api/src/routes/index.ts +4 -0
  38. package/api/src/routes/invoices.ts +44 -3
  39. package/api/src/routes/meter-events.ts +2 -1
  40. package/api/src/routes/payment-currencies.ts +1 -0
  41. package/api/src/routes/promotion-codes.ts +482 -0
  42. package/api/src/routes/subscriptions.ts +23 -2
  43. package/api/src/routes/vendor.ts +89 -2
  44. package/api/src/store/migrations/20250904-discount.ts +136 -0
  45. package/api/src/store/migrations/20250910-timestamp-fields.ts +116 -0
  46. package/api/src/store/migrations/20250916-add-description-fields.ts +30 -0
  47. package/api/src/store/migrations/20250918-add-vendor-extends.ts +20 -0
  48. package/api/src/store/models/checkout-session.ts +17 -2
  49. package/api/src/store/models/coupon.ts +144 -4
  50. package/api/src/store/models/discount.ts +23 -10
  51. package/api/src/store/models/index.ts +13 -2
  52. package/api/src/store/models/product-vendor.ts +6 -0
  53. package/api/src/store/models/promotion-code.ts +295 -18
  54. package/api/src/store/models/types.ts +30 -1
  55. package/api/tests/libs/session.spec.ts +48 -27
  56. package/blocklet.yml +1 -1
  57. package/package.json +20 -20
  58. package/src/app.tsx +2 -0
  59. package/src/components/customer/link.tsx +1 -1
  60. package/src/components/discount/discount-info.tsx +178 -0
  61. package/src/components/invoice/table.tsx +140 -48
  62. package/src/components/invoice-pdf/styles.ts +6 -0
  63. package/src/components/invoice-pdf/template.tsx +59 -33
  64. package/src/components/metadata/form.tsx +14 -5
  65. package/src/components/payment-link/actions.tsx +42 -0
  66. package/src/components/price/form.tsx +91 -65
  67. package/src/components/product/vendor-config.tsx +5 -3
  68. package/src/components/promotion/active-redemptions.tsx +534 -0
  69. package/src/components/promotion/currency-multi-select.tsx +350 -0
  70. package/src/components/promotion/currency-restrictions.tsx +117 -0
  71. package/src/components/promotion/product-select.tsx +292 -0
  72. package/src/components/promotion/promotion-code-form.tsx +534 -0
  73. package/src/components/subscription/portal/list.tsx +6 -1
  74. package/src/components/subscription/vendor-service-list.tsx +13 -2
  75. package/src/locales/en.tsx +227 -0
  76. package/src/locales/zh.tsx +222 -1
  77. package/src/pages/admin/billing/subscriptions/detail.tsx +5 -0
  78. package/src/pages/admin/products/coupons/applicable-products.tsx +166 -0
  79. package/src/pages/admin/products/coupons/create.tsx +612 -0
  80. package/src/pages/admin/products/coupons/detail.tsx +538 -0
  81. package/src/pages/admin/products/coupons/edit.tsx +127 -0
  82. package/src/pages/admin/products/coupons/index.tsx +210 -3
  83. package/src/pages/admin/products/index.tsx +22 -3
  84. package/src/pages/admin/products/products/detail.tsx +12 -2
  85. package/src/pages/admin/products/promotion-codes/actions.tsx +103 -0
  86. package/src/pages/admin/products/promotion-codes/create.tsx +235 -0
  87. package/src/pages/admin/products/promotion-codes/detail.tsx +416 -0
  88. package/src/pages/admin/products/promotion-codes/list.tsx +247 -0
  89. package/src/pages/admin/products/promotion-codes/verification-config.tsx +327 -0
  90. package/src/pages/admin/products/vendors/index.tsx +17 -5
  91. package/src/pages/customer/subscription/detail.tsx +5 -0
  92. package/vite.config.ts +4 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.20.11",
3
+ "version": "1.20.13",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
@@ -45,32 +45,32 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@abtnode/cron": "^1.16.52-beta-20250912-112002-e3499e9c",
48
- "@arcblock/did": "^1.24.9",
49
- "@arcblock/did-connect-react": "^3.1.40",
48
+ "@arcblock/did": "^1.25.1",
49
+ "@arcblock/did-connect-react": "^3.1.41",
50
50
  "@arcblock/did-connect-storage-nedb": "^1.8.0",
51
- "@arcblock/did-util": "^1.24.9",
52
- "@arcblock/jwt": "^1.24.9",
53
- "@arcblock/ux": "^3.1.40",
54
- "@arcblock/validator": "^1.24.9",
55
- "@blocklet/did-space-js": "^1.1.23",
51
+ "@arcblock/did-util": "^1.25.1",
52
+ "@arcblock/jwt": "^1.25.1",
53
+ "@arcblock/ux": "^3.1.41",
54
+ "@arcblock/validator": "^1.25.1",
55
+ "@blocklet/did-space-js": "^1.1.24",
56
56
  "@blocklet/error": "^0.2.5",
57
57
  "@blocklet/js-sdk": "^1.16.52-beta-20250912-112002-e3499e9c",
58
58
  "@blocklet/logger": "^1.16.52-beta-20250912-112002-e3499e9c",
59
- "@blocklet/payment-react": "1.20.11",
60
- "@blocklet/payment-vendor": "1.20.11",
59
+ "@blocklet/payment-react": "1.20.13",
60
+ "@blocklet/payment-vendor": "1.20.13",
61
61
  "@blocklet/sdk": "^1.16.52-beta-20250912-112002-e3499e9c",
62
- "@blocklet/ui-react": "^3.1.40",
63
- "@blocklet/uploader": "^0.2.10",
62
+ "@blocklet/ui-react": "^3.1.41",
63
+ "@blocklet/uploader": "^0.2.11",
64
64
  "@blocklet/xss": "^0.2.7",
65
65
  "@mui/icons-material": "^7.1.2",
66
66
  "@mui/lab": "7.0.0-beta.14",
67
67
  "@mui/material": "^7.1.2",
68
68
  "@mui/system": "^7.1.1",
69
- "@ocap/asset": "^1.24.9",
70
- "@ocap/client": "^1.24.9",
71
- "@ocap/mcrypto": "^1.24.9",
72
- "@ocap/util": "^1.24.9",
73
- "@ocap/wallet": "^1.24.9",
69
+ "@ocap/asset": "^1.25.1",
70
+ "@ocap/client": "^1.25.1",
71
+ "@ocap/mcrypto": "^1.25.1",
72
+ "@ocap/util": "^1.25.1",
73
+ "@ocap/wallet": "^1.25.1",
74
74
  "@stripe/react-stripe-js": "^2.9.0",
75
75
  "@stripe/stripe-js": "^2.4.0",
76
76
  "ahooks": "^3.8.5",
@@ -126,7 +126,7 @@
126
126
  "devDependencies": {
127
127
  "@abtnode/types": "^1.16.52-beta-20250912-112002-e3499e9c",
128
128
  "@arcblock/eslint-config-ts": "^0.3.3",
129
- "@blocklet/payment-types": "1.20.11",
129
+ "@blocklet/payment-types": "1.20.13",
130
130
  "@types/cookie-parser": "^1.4.9",
131
131
  "@types/cors": "^2.8.19",
132
132
  "@types/debug": "^4.1.12",
@@ -157,7 +157,7 @@
157
157
  "vite": "^7.0.0",
158
158
  "vite-node": "^3.2.4",
159
159
  "vite-plugin-babel-import": "^2.0.5",
160
- "vite-plugin-blocklet": "^0.10.1",
160
+ "vite-plugin-blocklet": "^0.11.0",
161
161
  "vite-plugin-node-polyfills": "^0.23.0",
162
162
  "vite-plugin-svgr": "^4.3.0",
163
163
  "vite-tsconfig-paths": "^5.1.4",
@@ -173,5 +173,5 @@
173
173
  "parser": "typescript"
174
174
  }
175
175
  },
176
- "gitHead": "b69becabf1721f6238cf24004537bee1e4ade860"
176
+ "gitHead": "0cbe918549f7d06561b5307fb947bfbbd2250984"
177
177
  }
package/src/app.tsx CHANGED
@@ -58,6 +58,8 @@ function App() {
58
58
  <Route key="admin-index" path="/admin" element={<AdminPage />} />,
59
59
  <Route key="admin-tabs" path="/admin/:group" element={<AdminPage />} />,
60
60
  <Route key="admin-sub" path="/admin/:group/:page" element={<AdminPage />} />,
61
+ <Route key="admin-detail" path="/admin/:group/:page/:id" element={<AdminPage />} />,
62
+ <Route key="admin-nested" path="/admin/:group/:page/:subpage/:id" element={<AdminPage />} />,
61
63
  <Route key="admin-fallback" path="/admin/*" element={<AdminPage />} />,
62
64
  <Route key="integrations-index" path="/integrations" element={<IntegrationsPage />} />
63
65
  <Route key="integrations-tabs" path="/integrations/:group" element={<IntegrationsPage />} />
@@ -40,7 +40,7 @@ export default function CustomerLink({
40
40
  popupInfoType={InfoType.Minimal}
41
41
  showDid={size !== 'small'}
42
42
  popupShowDid
43
- {...(customer.metadata.anonymous === true
43
+ {...(customer?.metadata?.anonymous === true
44
44
  ? {
45
45
  user: {
46
46
  fullName: customer.name || customer.email,
@@ -0,0 +1,178 @@
1
+ import { Box, Typography, Stack } from '@mui/material';
2
+ import { LocalOfferOutlined } from '@mui/icons-material';
3
+ import { formatTime, formatAmount, findCurrency, usePaymentContext, useMobile } from '@blocklet/payment-react';
4
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
5
+
6
+ type DiscountStats = {
7
+ subscription_id: string;
8
+ total_discount_records: number;
9
+ total_savings: Record<
10
+ string,
11
+ {
12
+ amount: string;
13
+ currency: any;
14
+ formattedAmount: string;
15
+ }
16
+ >;
17
+ coupons_used: string[];
18
+ promotion_codes_used: any[];
19
+ discount_records: any[];
20
+ };
21
+
22
+ type DiscountInfoProps = {
23
+ discountStats?: DiscountStats | null;
24
+ };
25
+
26
+ export default function DiscountInfo({ discountStats = null }: DiscountInfoProps) {
27
+ const { t } = useLocaleContext();
28
+ const { settings } = usePaymentContext();
29
+ const { isMobile } = useMobile();
30
+
31
+ if (!discountStats || discountStats.total_discount_records === 0) {
32
+ return null;
33
+ }
34
+
35
+ // Get primary coupon info
36
+ const primaryRecord = discountStats.discount_records?.[0];
37
+ const coupon = primaryRecord?.coupon;
38
+
39
+ if (!coupon) {
40
+ return null;
41
+ }
42
+
43
+ // Use coupon name instead of promotion code
44
+ const couponName = coupon.name || t('admin.coupon.discount');
45
+
46
+ // Format discount type
47
+ const getDiscountType = () => {
48
+ if (coupon.percent_off && coupon.percent_off > 0) {
49
+ return t('admin.coupon.couponTermsPercentage', { percent: coupon.percent_off });
50
+ }
51
+ if (coupon.amount_off && coupon.amount_off !== '0') {
52
+ const currency = findCurrency(settings.paymentMethods, coupon.currency_id) || settings.baseCurrency;
53
+ return t('admin.coupon.couponTermsFixedAmount', {
54
+ amount: formatAmount(coupon.amount_off, currency.decimal),
55
+ symbol: currency.symbol,
56
+ });
57
+ }
58
+ return t('admin.coupon.noDiscount');
59
+ };
60
+
61
+ // Format duration
62
+ const getDuration = () => {
63
+ if (coupon.duration === 'repeating' && coupon.duration_in_months) {
64
+ return t(`admin.coupon.couponTermsDuration.${coupon.duration}`, { months: coupon.duration_in_months });
65
+ }
66
+ return t(`admin.coupon.couponTermsDuration.${coupon.duration}`);
67
+ };
68
+
69
+ // Format total savings
70
+ const totalSavingsEntries = Object.entries(discountStats.total_savings);
71
+ const savingsText =
72
+ totalSavingsEntries.length > 0
73
+ ? totalSavingsEntries.map(([, savings]) => savings.formattedAmount).join(', ')
74
+ : '--';
75
+
76
+ // Get validity period
77
+ const getValidityPeriod = () => {
78
+ if (coupon.duration !== 'repeating') {
79
+ return '';
80
+ }
81
+ if (primaryRecord?.discount_start && primaryRecord?.discount_end) {
82
+ const startDate = formatTime(primaryRecord.discount_start * 1000);
83
+ const endDate = formatTime(primaryRecord.discount_end * 1000);
84
+ return `${startDate} ~ ${endDate}`;
85
+ }
86
+ return '';
87
+ };
88
+
89
+ return (
90
+ <Box
91
+ sx={{
92
+ bgcolor: 'grey.50',
93
+ borderRadius: 1.5,
94
+ p: 2.5,
95
+ my: 2,
96
+ border: '1px solid',
97
+ borderColor: 'divider',
98
+ }}>
99
+ <Stack spacing={1}>
100
+ <Stack
101
+ direction={isMobile ? 'column' : 'row'}
102
+ spacing={2}
103
+ sx={{
104
+ alignItems: isMobile ? 'flex-start' : 'center',
105
+ }}>
106
+ <Stack
107
+ direction="row"
108
+ spacing={1}
109
+ sx={{
110
+ alignItems: 'center',
111
+ flex: 1,
112
+ }}>
113
+ <LocalOfferOutlined sx={{ color: 'primary.main', fontSize: 20 }} />
114
+ <Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'text.primary' }}>
115
+ {couponName}
116
+ </Typography>
117
+ </Stack>
118
+
119
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
120
+ <Typography variant="body2" sx={{ fontWeight: 500, color: 'text.secondary' }}>
121
+ {getDiscountType()}
122
+ </Typography>
123
+ {getDuration() && (
124
+ <>
125
+ <Typography
126
+ variant="body2"
127
+ sx={{
128
+ color: 'text.disabled',
129
+ }}>
130
+ |
131
+ </Typography>
132
+ <Typography
133
+ variant="body2"
134
+ sx={{
135
+ color: 'text.secondary',
136
+ }}>
137
+ {getDuration()}
138
+ </Typography>
139
+ </>
140
+ )}
141
+ </Box>
142
+ </Stack>
143
+
144
+ <Stack
145
+ direction={isMobile ? 'column' : 'row'}
146
+ spacing={2}
147
+ sx={{
148
+ alignItems: isMobile ? 'flex-start' : 'center',
149
+ }}>
150
+ <Box sx={{ flex: 1 }}>
151
+ <Typography
152
+ variant="body2"
153
+ sx={{
154
+ color: 'text.secondary',
155
+ }}>
156
+ {t('admin.discount.totalSaved')}:{' '}
157
+ <Typography component="span" sx={{ color: 'success.main', fontWeight: 600, ml: 0.5 }}>
158
+ {savingsText}
159
+ </Typography>
160
+ </Typography>
161
+ </Box>
162
+
163
+ {getValidityPeriod() && (
164
+ <Box>
165
+ <Typography
166
+ variant="body2"
167
+ sx={{
168
+ color: 'text.primary',
169
+ }}>
170
+ {t('admin.coupon.discountPeriod')}: {getValidityPeriod()}
171
+ </Typography>
172
+ </Box>
173
+ )}
174
+ </Stack>
175
+ </Stack>
176
+ </Box>
177
+ );
178
+ }
@@ -63,7 +63,7 @@ export function getAppliedBalance(invoice: TInvoiceExpanded) {
63
63
  return '0';
64
64
  }
65
65
 
66
- export function getInvoiceRows(invoice: TInvoiceExpanded) {
66
+ export function getInvoiceRows(invoice: TInvoiceExpanded, t: (key: string) => string) {
67
67
  const detail: InvoiceDetailItem[] = invoice.lines.map((line) => {
68
68
  const price = line.quantity
69
69
  ? toBN(line.amount).div(toBN(line.quantity)).toString()
@@ -99,14 +99,61 @@ export function getInvoiceRows(invoice: TInvoiceExpanded) {
99
99
  {
100
100
  key: 'common.subtotal',
101
101
  value: formatAmount(invoice.subtotal, invoice.paymentCurrency.decimal),
102
- color: 'text.primary',
103
- },
104
- {
105
- key: 'common.total',
106
- value: formatAmount(invoice.total, invoice.paymentCurrency.decimal),
107
- color: 'text.primary',
102
+ color: 'text.secondary',
108
103
  },
109
104
  ];
105
+
106
+ // Add discount information from discountDetails
107
+ if ((invoice as any).discountDetails && (invoice as any).discountDetails.length > 0) {
108
+ (invoice as any).discountDetails.forEach((discount: any) => {
109
+ if (discount.coupon) {
110
+ let discountAmount = '0';
111
+
112
+ // Calculate discount amount from total_discount_amounts
113
+ if (invoice.total_discount_amounts && invoice.total_discount_amounts.length > 0) {
114
+ const matchingDiscount = invoice.total_discount_amounts.find(
115
+ (tda: any) => tda.discount === discount.id || tda.coupon === discount.coupon_id
116
+ );
117
+ if (matchingDiscount) {
118
+ discountAmount = matchingDiscount.amount.toString();
119
+ }
120
+ }
121
+
122
+ if (toBN(discountAmount).gt(toBN(0))) {
123
+ const couponName = discount.coupon.name || t('common.coupon');
124
+ const formattedDiscountAmount = formatAmount(discountAmount, invoice.paymentCurrency.decimal);
125
+
126
+ // Format discount display following Stripe style: coupon_name (discount_type)
127
+ let discountLabel = couponName;
128
+ if (discount.coupon.percent_off) {
129
+ const discountText = (t as any)('admin.coupon.couponTermsPercentage', {
130
+ percent: discount.coupon.percent_off,
131
+ });
132
+ discountLabel = `${couponName} (${discountText})`;
133
+ } else if (discount.coupon.amount_off) {
134
+ const fixedAmount = formatAmount(discount.coupon.amount_off, invoice.paymentCurrency.decimal);
135
+ const discountText = (t as any)('admin.coupon.couponTermsFixedAmount', {
136
+ amount: fixedAmount,
137
+ symbol: invoice.paymentCurrency.symbol,
138
+ });
139
+ discountLabel = `${couponName} (${discountText})`;
140
+ }
141
+
142
+ summary.push({
143
+ key: discountLabel,
144
+ value: `-${formattedDiscountAmount}`,
145
+ color: 'text.secondary',
146
+ });
147
+ }
148
+ }
149
+ });
150
+ }
151
+
152
+ summary.push({
153
+ key: 'common.total',
154
+ value: formatAmount(invoice.total, invoice.paymentCurrency.decimal),
155
+ color: 'text.secondary',
156
+ });
110
157
  if (invoice.amount_paid !== '0') {
111
158
  summary.push({
112
159
  key: 'payment.customer.invoice.amountPaid',
@@ -126,7 +173,7 @@ export function getInvoiceRows(invoice: TInvoiceExpanded) {
126
173
  summary.push({
127
174
  key: 'payment.customer.invoice.amountDue',
128
175
  value: formatAmount(invoice.amount_remaining, invoice.paymentCurrency.decimal),
129
- color: 'text.primary',
176
+ color: invoice.amount_remaining !== '0' ? 'text.primary' : 'text.secondary',
130
177
  });
131
178
 
132
179
  return {
@@ -137,7 +184,7 @@ export function getInvoiceRows(invoice: TInvoiceExpanded) {
137
184
 
138
185
  export default function InvoiceTable({ invoice, simple = false, emptyNodeText = '' }: Props) {
139
186
  const { t, locale } = useLocaleContext();
140
- const { detail, summary } = getInvoiceRows(invoice);
187
+ const { detail, summary } = getInvoiceRows(invoice, t);
141
188
  const [state, setState] = useSetState({
142
189
  subscriptionId: '',
143
190
  subscriptionItemId: '',
@@ -246,6 +293,34 @@ export default function InvoiceTable({ invoice, simple = false, emptyNodeText =
246
293
  width: 200,
247
294
  align: 'right',
248
295
  },
296
+ {
297
+ label: t('common.discount'),
298
+ name: 'discount',
299
+ width: 200,
300
+ align: 'right',
301
+ options: {
302
+ customBodyRenderLite: (_: string, index: number) => {
303
+ const item = detail[index] as InvoiceDetailItem;
304
+ // Calculate discount amount for this line item
305
+ let itemDiscountAmount = toBN(0);
306
+ if (item.raw.discount_amounts && item.raw.discount_amounts.length > 0) {
307
+ item.raw.discount_amounts.forEach((discount: any) => {
308
+ if (discount.amount) {
309
+ itemDiscountAmount = itemDiscountAmount.add(toBN(discount.amount));
310
+ }
311
+ });
312
+ }
313
+
314
+ return (
315
+ <Typography component="span" sx={{ color: 'text.secondary' }}>
316
+ {itemDiscountAmount.gt(toBN(0))
317
+ ? `-${formatAmount(itemDiscountAmount.toString(), invoice.paymentCurrency.decimal)}`
318
+ : '-'}
319
+ </Typography>
320
+ );
321
+ },
322
+ },
323
+ },
249
324
  {
250
325
  label: t('common.amount'),
251
326
  name: 'amount',
@@ -299,49 +374,72 @@ export default function InvoiceTable({ invoice, simple = false, emptyNodeText =
299
374
  emptyNodeText={emptyNodeText || t('payment.customer.invoice.emptyList')}
300
375
  />
301
376
  {!isEmpty(detail) && invoice.status !== 'void' && (
302
- <Stack
377
+ <Box
303
378
  className="invoice-summary"
304
379
  sx={{
305
380
  display: 'flex',
306
- flexDirection: {
307
- xs: 'column',
308
- md: 'row',
381
+ flexDirection: 'column',
382
+ alignItems: 'flex-end',
383
+ float: 'right',
384
+ pr: 2,
385
+ mt: {
386
+ xs: 1,
387
+ md: 1,
309
388
  },
310
- justifyContent: 'flex-end',
311
- alignItems: {
312
- xs: 'flex-end',
313
- md: 'center',
389
+ width: {
390
+ xs: '100%',
391
+ md: 'auto',
314
392
  },
315
- gap: {
316
- xs: 1,
317
- md: 3,
393
+ minWidth: {
394
+ md: '280px',
318
395
  },
319
- mt: {
320
- xs: 1,
321
- md: 2,
396
+ maxWidth: {
397
+ xs: '100%',
398
+ md: '400px',
322
399
  },
323
400
  }}>
324
- {summary.map((line) => (
325
- <Box key={line.key}>
326
- <Typography
327
- component="span"
328
- variant="body1"
329
- sx={{
330
- color: 'text.secondary',
331
- }}>
332
- {t(line.key)}:&nbsp;
333
- </Typography>
334
- <Typography
335
- component="span"
336
- variant="body1"
401
+ {summary.map((line, index) => {
402
+ const isTotal = line.key === 'common.total';
403
+ const showDivider = isTotal && index > 0;
404
+
405
+ return (
406
+ <Stack
407
+ key={line.key}
408
+ direction="row"
337
409
  sx={{
338
- color: 'text.primary',
410
+ justifyContent: 'flex-end',
411
+ width: '100%',
412
+ py: 0.5,
413
+
414
+ ...(showDivider && {
415
+ borderTop: '1px solid',
416
+ borderTopColor: 'divider',
417
+ pt: 1,
418
+ mt: 0.5,
419
+ }),
339
420
  }}>
340
- {line.value}
341
- </Typography>
342
- </Box>
343
- ))}
344
- </Stack>
421
+ <Typography
422
+ variant="body2"
423
+ sx={{
424
+ color: 'text.primary',
425
+ fontWeight: isTotal ? 500 : 400,
426
+ }}>
427
+ {line.key.startsWith('common.') || line.key.startsWith('payment.') ? t(line.key) : line.key}
428
+ </Typography>
429
+ <Typography
430
+ variant="body2"
431
+ sx={{
432
+ color: 'text.primary',
433
+ fontWeight: isTotal ? 500 : 400,
434
+ minWidth: '80px',
435
+ textAlign: 'right',
436
+ }}>
437
+ {line.value}
438
+ </Typography>
439
+ </Stack>
440
+ );
441
+ })}
442
+ </Box>
345
443
  )}
346
444
  {state.subscriptionId && state.subscriptionItemId && (
347
445
  <UsageRecordDialog
@@ -359,9 +457,6 @@ export default function InvoiceTable({ invoice, simple = false, emptyNodeText =
359
457
  }
360
458
 
361
459
  const Root = styled(Box)`
362
- .invoice-summary {
363
- padding-right: 16px;
364
- }
365
460
  @media (max-width: ${({ theme }) => theme.breakpoints.values.md}px) {
366
461
  .MuiTable-root > .MuiTableBody-root > .MuiTableRow-root > td.MuiTableCell-root {
367
462
  > div {
@@ -370,8 +465,5 @@ const Root = styled(Box)`
370
465
  font-size: 14px;
371
466
  }
372
467
  }
373
- .invoice-summary {
374
- padding-right: 20px;
375
- }
376
468
  }
377
469
  `;
@@ -4,6 +4,7 @@ export const colorDark = '#222';
4
4
  export const colorDark2 = '#888';
5
5
  export const colorGray = '#e3e3e3';
6
6
  export const colorWhite = '#fff';
7
+ export const colorGreen = '#4caf50';
7
8
 
8
9
  export const pdfStyles: InvoiceStyles = {
9
10
  template: {
@@ -29,6 +30,9 @@ export const pdfStyles: InvoiceStyles = {
29
30
  gray: {
30
31
  color: colorDark2,
31
32
  },
33
+ green: {
34
+ color: colorGreen,
35
+ },
32
36
  'bg-dark': {
33
37
  backgroundColor: colorDark2,
34
38
  },
@@ -55,8 +59,10 @@ export const pdfStyles: InvoiceStyles = {
55
59
  'w-60': { width: '60%' },
56
60
  'w-40': { width: '40%' },
57
61
  'w-48': { width: '48%' },
62
+ 'w-38': { width: '38%' },
58
63
  'w-17': { width: '17%' },
59
64
  'w-18': { width: '18%' },
65
+ 'w-15': { width: '15%' },
60
66
  row: {
61
67
  borderBottom: `1px solid ${colorGray}`,
62
68
  marginBottom: '2px',