payment-kit 1.18.56 → 1.19.1

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 (214) hide show
  1. package/.eslintrc.js +6 -0
  2. package/api/src/crons/index.ts +8 -0
  3. package/api/src/index.ts +4 -0
  4. package/api/src/libs/credit-grant.ts +146 -0
  5. package/api/src/libs/env.ts +1 -0
  6. package/api/src/libs/invoice.ts +4 -3
  7. package/api/src/libs/notification/template/base.ts +388 -2
  8. package/api/src/libs/notification/template/customer-credit-grant-granted.ts +149 -0
  9. package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +151 -0
  10. package/api/src/libs/notification/template/customer-credit-insufficient.ts +254 -0
  11. package/api/src/libs/notification/template/subscription-canceled.ts +193 -202
  12. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +215 -237
  13. package/api/src/libs/notification/template/subscription-renewed.ts +130 -200
  14. package/api/src/libs/notification/template/subscription-succeeded.ts +100 -202
  15. package/api/src/libs/notification/template/subscription-trial-start.ts +142 -188
  16. package/api/src/libs/notification/template/subscription-trial-will-end.ts +146 -174
  17. package/api/src/libs/notification/template/subscription-upgraded.ts +96 -192
  18. package/api/src/libs/notification/template/subscription-will-canceled.ts +94 -135
  19. package/api/src/libs/notification/template/subscription-will-renew.ts +220 -245
  20. package/api/src/libs/payment.ts +69 -0
  21. package/api/src/libs/queue/index.ts +3 -2
  22. package/api/src/libs/session.ts +8 -0
  23. package/api/src/libs/subscription.ts +74 -3
  24. package/api/src/libs/ws.ts +23 -1
  25. package/api/src/locales/en.ts +33 -0
  26. package/api/src/locales/zh.ts +31 -0
  27. package/api/src/queues/credit-consume.ts +715 -0
  28. package/api/src/queues/credit-grant.ts +572 -0
  29. package/api/src/queues/notification.ts +173 -128
  30. package/api/src/queues/payment.ts +210 -122
  31. package/api/src/queues/subscription.ts +179 -0
  32. package/api/src/routes/checkout-sessions.ts +157 -9
  33. package/api/src/routes/connect/shared.ts +3 -2
  34. package/api/src/routes/credit-grants.ts +241 -0
  35. package/api/src/routes/credit-transactions.ts +208 -0
  36. package/api/src/routes/index.ts +8 -0
  37. package/api/src/routes/meter-events.ts +347 -0
  38. package/api/src/routes/meters.ts +219 -0
  39. package/api/src/routes/payment-currencies.ts +14 -2
  40. package/api/src/routes/payment-links.ts +1 -1
  41. package/api/src/routes/payment-methods.ts +14 -2
  42. package/api/src/routes/prices.ts +43 -0
  43. package/api/src/routes/pricing-table.ts +13 -7
  44. package/api/src/routes/products.ts +63 -4
  45. package/api/src/routes/settings.ts +1 -1
  46. package/api/src/routes/subscriptions.ts +4 -0
  47. package/api/src/store/migrations/20250610-billing-credit.ts +43 -0
  48. package/api/src/store/models/credit-grant.ts +486 -0
  49. package/api/src/store/models/credit-transaction.ts +268 -0
  50. package/api/src/store/models/customer.ts +8 -0
  51. package/api/src/store/models/index.ts +52 -1
  52. package/api/src/store/models/meter-event.ts +423 -0
  53. package/api/src/store/models/meter.ts +176 -0
  54. package/api/src/store/models/payment-currency.ts +66 -14
  55. package/api/src/store/models/price.ts +6 -0
  56. package/api/src/store/models/product.ts +2 -2
  57. package/api/src/store/models/subscription.ts +24 -0
  58. package/api/src/store/models/types.ts +28 -2
  59. package/api/tests/libs/subscription.spec.ts +53 -0
  60. package/blocklet.yml +9 -1
  61. package/package.json +57 -58
  62. package/scripts/sdk.js +233 -1
  63. package/src/app.tsx +10 -0
  64. package/src/components/actions.tsx +22 -9
  65. package/src/components/balance-list.tsx +40 -12
  66. package/src/components/collapse.tsx +33 -15
  67. package/src/components/copyable.tsx +8 -7
  68. package/src/components/currency.tsx +15 -7
  69. package/src/components/customer/actions.tsx +1 -5
  70. package/src/components/customer/credit-grant-item-list.tsx +99 -0
  71. package/src/components/customer/credit-overview.tsx +233 -0
  72. package/src/components/customer/form.tsx +7 -2
  73. package/src/components/customer/link.tsx +4 -12
  74. package/src/components/customer/notification-preference.tsx +18 -9
  75. package/src/components/customer/overdraft-protection.tsx +112 -41
  76. package/src/components/drawer-form.tsx +42 -18
  77. package/src/components/error.tsx +1 -5
  78. package/src/components/event/list.tsx +9 -10
  79. package/src/components/filter-toolbar.tsx +20 -19
  80. package/src/components/info-card.tsx +32 -18
  81. package/src/components/info-metric.tsx +16 -6
  82. package/src/components/info-row-group.tsx +1 -7
  83. package/src/components/info-row.tsx +30 -24
  84. package/src/components/invoice/action.tsx +1 -7
  85. package/src/components/invoice/list.tsx +34 -26
  86. package/src/components/invoice/recharge.tsx +5 -7
  87. package/src/components/invoice/table.tsx +17 -12
  88. package/src/components/layout/user.tsx +1 -1
  89. package/src/components/metadata/form.tsx +290 -94
  90. package/src/components/metadata/list.tsx +11 -3
  91. package/src/components/meter/actions.tsx +101 -0
  92. package/src/components/meter/add-usage-dialog.tsx +239 -0
  93. package/src/components/meter/events-list.tsx +657 -0
  94. package/src/components/meter/form.tsx +245 -0
  95. package/src/components/meter/products.tsx +264 -0
  96. package/src/components/meter/usage-guide.tsx +174 -0
  97. package/src/components/passport/actions.tsx +9 -4
  98. package/src/components/payment-currency/add.tsx +16 -3
  99. package/src/components/payment-currency/form.tsx +14 -6
  100. package/src/components/payment-intent/actions.tsx +24 -16
  101. package/src/components/payment-intent/list.tsx +30 -9
  102. package/src/components/payment-link/actions.tsx +1 -5
  103. package/src/components/payment-link/after-pay.tsx +4 -2
  104. package/src/components/payment-link/before-pay.tsx +14 -4
  105. package/src/components/payment-link/item.tsx +27 -6
  106. package/src/components/payment-link/preview.tsx +9 -9
  107. package/src/components/payment-link/product-select.tsx +69 -15
  108. package/src/components/payment-method/arcblock.tsx +8 -1
  109. package/src/components/payment-method/base.tsx +8 -1
  110. package/src/components/payment-method/bitcoin.tsx +8 -1
  111. package/src/components/payment-method/ethereum.tsx +8 -1
  112. package/src/components/payment-method/evm-rpc-input.tsx +11 -7
  113. package/src/components/payment-method/form.tsx +2 -7
  114. package/src/components/payment-method/stripe.tsx +2 -0
  115. package/src/components/payouts/actions.tsx +1 -5
  116. package/src/components/payouts/list.tsx +30 -10
  117. package/src/components/payouts/portal/list.tsx +11 -9
  118. package/src/components/price/currency-select.tsx +63 -32
  119. package/src/components/price/form.tsx +895 -370
  120. package/src/components/price/upsell-select.tsx +10 -2
  121. package/src/components/price/upsell.tsx +7 -2
  122. package/src/components/pricing-table/actions.tsx +1 -5
  123. package/src/components/pricing-table/customer-settings.tsx +5 -1
  124. package/src/components/pricing-table/payment-settings.tsx +14 -4
  125. package/src/components/pricing-table/preview.tsx +9 -9
  126. package/src/components/pricing-table/price-item.tsx +6 -1
  127. package/src/components/pricing-table/product-item.tsx +6 -1
  128. package/src/components/pricing-table/product-settings.tsx +17 -4
  129. package/src/components/product/actions.tsx +1 -5
  130. package/src/components/product/add-price.tsx +9 -7
  131. package/src/components/product/create.tsx +8 -9
  132. package/src/components/product/cross-sell-select.tsx +5 -1
  133. package/src/components/product/cross-sell.tsx +7 -2
  134. package/src/components/product/edit-price.tsx +21 -12
  135. package/src/components/product/features.tsx +26 -6
  136. package/src/components/product/form.tsx +115 -72
  137. package/src/components/progress-bar.tsx +1 -1
  138. package/src/components/refund/actions.tsx +1 -7
  139. package/src/components/refund/list.tsx +31 -18
  140. package/src/components/section/header.tsx +12 -14
  141. package/src/components/subscription/actions/cancel.tsx +22 -5
  142. package/src/components/subscription/actions/index.tsx +9 -10
  143. package/src/components/subscription/actions/pause.tsx +32 -6
  144. package/src/components/subscription/actions/slash-stake.tsx +5 -3
  145. package/src/components/subscription/description.tsx +12 -8
  146. package/src/components/subscription/items/index.tsx +31 -16
  147. package/src/components/subscription/items/usage-records.tsx +19 -5
  148. package/src/components/subscription/list.tsx +5 -7
  149. package/src/components/subscription/metrics.tsx +62 -15
  150. package/src/components/subscription/portal/actions.tsx +78 -71
  151. package/src/components/subscription/portal/cancel.tsx +10 -3
  152. package/src/components/subscription/portal/list.tsx +48 -26
  153. package/src/components/uploader.tsx +5 -13
  154. package/src/components/webhook/attempts.tsx +51 -16
  155. package/src/components/webhook/request-info.tsx +8 -6
  156. package/src/contexts/products.tsx +27 -10
  157. package/src/hooks/subscription.ts +34 -0
  158. package/src/libs/meter-utils.ts +196 -0
  159. package/src/libs/util.ts +4 -0
  160. package/src/locales/en.tsx +385 -4
  161. package/src/locales/zh.tsx +364 -0
  162. package/src/pages/admin/billing/index.tsx +61 -33
  163. package/src/pages/admin/billing/invoices/detail.tsx +49 -13
  164. package/src/pages/admin/billing/meters/create.tsx +60 -0
  165. package/src/pages/admin/billing/meters/detail.tsx +435 -0
  166. package/src/pages/admin/billing/meters/index.tsx +210 -0
  167. package/src/pages/admin/billing/meters/meter-event.tsx +346 -0
  168. package/src/pages/admin/billing/subscriptions/detail.tsx +90 -25
  169. package/src/pages/admin/customers/customers/credit-grant/detail.tsx +391 -0
  170. package/src/pages/admin/customers/customers/detail.tsx +67 -14
  171. package/src/pages/admin/customers/customers/index.tsx +6 -1
  172. package/src/pages/admin/customers/index.tsx +5 -0
  173. package/src/pages/admin/developers/events/detail.tsx +37 -11
  174. package/src/pages/admin/developers/index.tsx +1 -1
  175. package/src/pages/admin/developers/webhooks/detail.tsx +41 -11
  176. package/src/pages/admin/index.tsx +15 -2
  177. package/src/pages/admin/overview.tsx +107 -19
  178. package/src/pages/admin/payments/intents/detail.tsx +58 -14
  179. package/src/pages/admin/payments/payouts/detail.tsx +63 -15
  180. package/src/pages/admin/payments/refunds/detail.tsx +58 -14
  181. package/src/pages/admin/products/index.tsx +11 -4
  182. package/src/pages/admin/products/links/create.tsx +22 -4
  183. package/src/pages/admin/products/links/detail.tsx +43 -14
  184. package/src/pages/admin/products/passports/index.tsx +23 -4
  185. package/src/pages/admin/products/prices/actions.tsx +16 -9
  186. package/src/pages/admin/products/prices/detail.tsx +73 -14
  187. package/src/pages/admin/products/prices/list.tsx +15 -3
  188. package/src/pages/admin/products/pricing-tables/create.tsx +45 -12
  189. package/src/pages/admin/products/pricing-tables/detail.tsx +45 -14
  190. package/src/pages/admin/products/products/create.tsx +233 -54
  191. package/src/pages/admin/products/products/detail.tsx +74 -18
  192. package/src/pages/admin/settings/index.tsx +8 -1
  193. package/src/pages/admin/settings/payment-methods/index.tsx +87 -19
  194. package/src/pages/admin/settings/vault-config/edit-form.tsx +42 -28
  195. package/src/pages/admin/settings/vault-config/index.tsx +57 -10
  196. package/src/pages/customer/credit-grant/detail.tsx +308 -0
  197. package/src/pages/customer/index.tsx +76 -17
  198. package/src/pages/customer/invoice/detail.tsx +63 -14
  199. package/src/pages/customer/invoice/past-due.tsx +11 -3
  200. package/src/pages/customer/payout/detail.tsx +56 -13
  201. package/src/pages/customer/recharge/account.tsx +78 -18
  202. package/src/pages/customer/recharge/subscription.tsx +86 -25
  203. package/src/pages/customer/refund/list.tsx +60 -24
  204. package/src/pages/customer/subscription/change-payment.tsx +17 -6
  205. package/src/pages/customer/subscription/change-plan.tsx +34 -7
  206. package/src/pages/customer/subscription/detail.tsx +134 -34
  207. package/src/pages/customer/subscription/embed.tsx +25 -5
  208. package/src/pages/home.tsx +26 -4
  209. package/src/pages/integrations/donations/edit-form.tsx +25 -9
  210. package/src/pages/integrations/donations/index.tsx +26 -9
  211. package/src/pages/integrations/donations/preview.tsx +59 -15
  212. package/src/pages/integrations/index.tsx +10 -1
  213. package/src/pages/integrations/overview.tsx +78 -17
  214. package/vite.config.ts +60 -30
@@ -81,12 +81,20 @@ export default function UpsellSelect({ price, onSelect, onAdd }: Props) {
81
81
  onChange={onSelectPrice}
82
82
  MenuProps={{ style: { maxHeight: 480 } }}>
83
83
  <MenuItem value="empty">
84
- <Stack direction="row" alignItems="center">
84
+ <Stack
85
+ direction="row"
86
+ sx={{
87
+ alignItems: 'center',
88
+ }}>
85
89
  <Typography>{t('admin.price.find')}</Typography>
86
90
  </Stack>
87
91
  </MenuItem>
88
92
  <MenuItem value="add">
89
- <Stack direction="row" alignItems="center">
93
+ <Stack
94
+ direction="row"
95
+ sx={{
96
+ alignItems: 'center',
97
+ }}>
90
98
  <AddOutlined />
91
99
  <Typography>{t('admin.price.add')}</Typography>
92
100
  </Stack>
@@ -48,7 +48,12 @@ export function UpsellForm({ data, onChange }: { data: TPriceExpanded; onChange:
48
48
 
49
49
  if (data.upsell?.upsells_to_id) {
50
50
  return (
51
- <Stack spacing={1} direction="row" alignItems="center">
51
+ <Stack
52
+ spacing={1}
53
+ direction="row"
54
+ sx={{
55
+ alignItems: 'center',
56
+ }}>
52
57
  <Typography>{formatPrice(data.upsell.upsells_to, defaultCurrency)}</Typography>
53
58
  <IconButton size="small" sx={{ ml: 1 }} onClick={onRemoveUpsell}>
54
59
  <DeleteOutlineOutlined color="error" sx={{ opacity: 0.75 }} />
@@ -63,7 +68,7 @@ export function UpsellForm({ data, onChange }: { data: TPriceExpanded; onChange:
63
68
  export default function PriceUpsell({ data, onChange }: { data: TPriceExpanded; onChange: Function }) {
64
69
  return (
65
70
  <Grid container>
66
- <Grid item xs={12} md={12}>
71
+ <Grid size={{ xs: 12, md: 12 }}>
67
72
  <InfoRow label="" value={<UpsellForm data={data} onChange={onChange} />} showLabel={false} />
68
73
  </Grid>
69
74
  </Grid>
@@ -18,11 +18,7 @@ type Props = {
18
18
  variant?: LiteralUnion<'compact' | 'normal', string>;
19
19
  };
20
20
 
21
- PricingTableActions.defaultProps = {
22
- variant: 'compact',
23
- };
24
-
25
- export default function PricingTableActions({ data, variant, onChange }: Props) {
21
+ export default function PricingTableActions({ data, variant = 'compact', onChange }: Props) {
26
22
  const { t } = useLocaleContext();
27
23
  const [state, setState] = useSetState({
28
24
  action: '',
@@ -5,7 +5,11 @@ export default function PricingTableCustomerSettings() {
5
5
  const { t } = useLocaleContext();
6
6
 
7
7
  return (
8
- <Stack spacing={2} alignItems="flex-start">
8
+ <Stack
9
+ spacing={2}
10
+ sx={{
11
+ alignItems: 'flex-start',
12
+ }}>
9
13
  <Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
10
14
  {t('admin.pricingTable.customer')}
11
15
  </Typography>
@@ -27,7 +27,11 @@ export function PricePaymentSettings({ index }: { index: number }) {
27
27
  const values = getValues();
28
28
 
29
29
  return (
30
- <Stack spacing={2} mb={2}>
30
+ <Stack
31
+ spacing={2}
32
+ sx={{
33
+ mb: 2,
34
+ }}>
31
35
  <Controller
32
36
  name={getFieldName('billing_address_collection')}
33
37
  control={control}
@@ -126,8 +130,10 @@ export function PricePaymentSettings({ index }: { index: number }) {
126
130
  size="small"
127
131
  error={!!get(errors, field.name)}
128
132
  helperText={(get(errors, field.name)?.message as string) || t('admin.paymentLink.customMessageTip')}
129
- inputProps={{
130
- maxLength: 200,
133
+ slotProps={{
134
+ htmlInput: {
135
+ maxLength: 200,
136
+ },
131
137
  }}
132
138
  />
133
139
  )}
@@ -259,7 +265,11 @@ export default function PricingTablePaymentSettings() {
259
265
  const grouped = groupPricingTableItems(items.fields);
260
266
 
261
267
  return (
262
- <Stack spacing={2} alignItems="flex-start">
268
+ <Stack
269
+ spacing={2}
270
+ sx={{
271
+ alignItems: 'flex-start',
272
+ }}>
263
273
  <Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
264
274
  {t('admin.paymentLink.products')}
265
275
  </Typography>
@@ -1,10 +1,16 @@
1
- import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
1
+ import { useEffect, useImperativeHandle, useRef, useState } from 'react';
2
2
  import { useFullscreen, useSize } from 'ahooks';
3
3
  import IframeResizer from 'iframe-resizer-react';
4
4
  import { useTheme } from '@mui/material';
5
5
  import Chrome from '../payment-link/chrome';
6
6
 
7
- const PricingTablePreview = forwardRef(({ id, version = 1 }: { id: string; version?: number }, ref) => {
7
+ export default function PricingTablePreview({
8
+ ref = undefined,
9
+ id,
10
+ version = 1,
11
+ }: { id: string; version?: number } & {
12
+ ref?: React.RefObject<unknown | null>;
13
+ }) {
8
14
  const innerRef = useRef(null);
9
15
  const size = useSize(innerRef);
10
16
  const theme = useTheme();
@@ -50,10 +56,4 @@ const PricingTablePreview = forwardRef(({ id, version = 1 }: { id: string; versi
50
56
  )}
51
57
  </div>
52
58
  );
53
- });
54
-
55
- PricingTablePreview.defaultProps = {
56
- version: 1,
57
- };
58
-
59
- export default PricingTablePreview;
59
+ }
@@ -26,7 +26,12 @@ export default function PriceItem({ prefix, price, onRemove }: Props) {
26
26
  const currency = findCurrency(settings.paymentMethods, price?.currency_id ?? '') || settings.baseCurrency;
27
27
  return (
28
28
  <Box sx={{ width: '100%' }}>
29
- <Stack direction="row" alignItems="center" justifyContent="space-between">
29
+ <Stack
30
+ direction="row"
31
+ sx={{
32
+ alignItems: 'center',
33
+ justifyContent: 'space-between',
34
+ }}>
30
35
  <Typography>{formatPrice(price, currency!)}</Typography>
31
36
  <IconButton size="small" onClick={onRemove}>
32
37
  <DeleteOutlineOutlined color="inherit" sx={{ opacity: 0.75 }} />
@@ -63,7 +63,12 @@ export default function ProductItem({ product, prices, valid, onUpdate, onRemove
63
63
  />
64
64
  </ClickBoundary>
65
65
  <InfoCard logo={product.images[0]} name={product.name} description={product.description} />
66
- <Stack direction="column" spacing={2} alignItems="flex-start">
66
+ <Stack
67
+ direction="column"
68
+ spacing={2}
69
+ sx={{
70
+ alignItems: 'flex-start',
71
+ }}>
67
72
  {prices.map((x: any) => (
68
73
  <PriceItem
69
74
  key={x.index}
@@ -14,7 +14,7 @@ import ProductItem from './product-item';
14
14
  export default function PricingTableProductSettings({
15
15
  triggerError = () => {},
16
16
  }: {
17
- triggerError: (keys: { [key: string]: boolean }) => void;
17
+ triggerError?: (keys: { [key: string]: boolean }) => void;
18
18
  }) {
19
19
  const { t } = useLocaleContext();
20
20
  const [params, setParams] = useSearchParams();
@@ -121,7 +121,11 @@ export default function PricingTableProductSettings({
121
121
  }, [items.fields, products]);
122
122
 
123
123
  return (
124
- <Stack spacing={2} alignItems="flex-start">
124
+ <Stack
125
+ spacing={2}
126
+ sx={{
127
+ alignItems: 'flex-start',
128
+ }}>
125
129
  <Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
126
130
  {t('admin.paymentLink.products')}
127
131
  </Typography>
@@ -148,7 +152,11 @@ export default function PricingTableProductSettings({
148
152
  );
149
153
  })}
150
154
  {items.fields.some((_, index) => !isPriceCurrencyAligned(items.fields as any[], products, index)) && (
151
- <Typography color="error" fontSize="small">
155
+ <Typography
156
+ color="error"
157
+ sx={{
158
+ fontSize: 'small',
159
+ }}>
152
160
  {t('admin.paymentLink.currencyNotAligned')}
153
161
  </Typography>
154
162
  )}
@@ -188,7 +196,12 @@ export default function PricingTableProductSettings({
188
196
  )}
189
197
  />
190
198
  {highlight && (
191
- <Stack direction="row" alignItems="center" spacing={0.5}>
199
+ <Stack
200
+ direction="row"
201
+ spacing={0.5}
202
+ sx={{
203
+ alignItems: 'center',
204
+ }}>
192
205
  <Controller
193
206
  name="highlight_product_id"
194
207
  control={control}
@@ -17,11 +17,7 @@ type ProductActionProps = {
17
17
  variant?: LiteralUnion<'compact' | 'normal', string>;
18
18
  };
19
19
 
20
- ProductActions.defaultProps = {
21
- variant: 'compact',
22
- };
23
-
24
- export default function ProductActions({ data, variant, onChange }: ProductActionProps) {
20
+ export default function ProductActions({ data, variant = 'compact', onChange }: ProductActionProps) {
25
21
  const isLocked = data.locked || (!isEmpty(data.prices) && data.prices.some((x) => x.locked));
26
22
  const { t } = useLocaleContext();
27
23
  const [state, setState] = useSetState({
@@ -6,16 +6,16 @@ import type { EventHandler } from 'react';
6
6
  import { FormProvider, useForm } from 'react-hook-form';
7
7
 
8
8
  import PriceForm, { DEFAULT_PRICE, Price } from '../price/form';
9
+ import { ProductsProvider } from '../../contexts/products';
9
10
 
10
- export default function AddPrice({
11
- loading,
12
- onSave,
13
- onCancel,
14
- }: {
11
+ interface AddPriceProps {
15
12
  loading: boolean;
16
13
  onSave: EventHandler<any>;
17
14
  onCancel: EventHandler<any>;
18
- }) {
15
+ productType?: string;
16
+ }
17
+
18
+ export default function AddPrice({ loading, onSave, onCancel, productType = '' }: AddPriceProps) {
19
19
  const { t } = useLocaleContext();
20
20
  const { settings } = usePaymentContext();
21
21
  const methods = useForm<Price>({
@@ -63,7 +63,9 @@ export default function AddPrice({
63
63
  </Stack>
64
64
  }>
65
65
  <FormProvider {...methods}>
66
- <PriceForm />
66
+ <ProductsProvider>
67
+ <PriceForm productType={productType} />
68
+ </ProductsProvider>
67
69
  </FormProvider>
68
70
  </Dialog>
69
71
  );
@@ -9,9 +9,10 @@ import { FormProvider, useFieldArray, useForm } from 'react-hook-form';
9
9
 
10
10
  import PriceForm, { DEFAULT_PRICE } from '../price/form';
11
11
  import ProductForm, { Product } from './form';
12
+ import { ProductsProvider } from '../../contexts/products';
12
13
 
13
14
  export default function CreateProduct({
14
- simple,
15
+ simple = true,
15
16
  onSave,
16
17
  onCancel,
17
18
  }: {
@@ -92,15 +93,13 @@ export default function CreateProduct({
92
93
  </Stack>
93
94
  }>
94
95
  <FormProvider {...methods}>
95
- <ProductForm simple={simple} />
96
- {prices.fields.map((price, index) => (
97
- <PriceForm key={price.id} prefix={`prices.${index}`} simple={simple} />
98
- ))}
96
+ <ProductsProvider>
97
+ <ProductForm simple={simple} />
98
+ {prices.fields.map((price, index) => (
99
+ <PriceForm key={price.id} prefix={`prices.${index}`} simple={simple} />
100
+ ))}
101
+ </ProductsProvider>
99
102
  </FormProvider>
100
103
  </Dialog>
101
104
  );
102
105
  }
103
-
104
- CreateProduct.defaultProps = {
105
- simple: true,
106
- };
@@ -39,7 +39,11 @@ export default function CrossSellSelect({ data, onSelect, valid }: Props) {
39
39
  onChange={onSelectPrice}
40
40
  MenuProps={{ style: { maxHeight: 480 } }}>
41
41
  <MenuItem value="empty">
42
- <Stack direction="row" alignItems="center">
42
+ <Stack
43
+ direction="row"
44
+ sx={{
45
+ alignItems: 'center',
46
+ }}>
43
47
  <Typography>{t('admin.product.find')}</Typography>
44
48
  </Stack>
45
49
  </MenuItem>
@@ -64,7 +64,12 @@ export function CrossSellForm({ data, onChange }: { data: TProductExpanded; onCh
64
64
  if (data.cross_sell?.cross_sells_to_id) {
65
65
  const to = data.cross_sell.cross_sells_to;
66
66
  return (
67
- <Stack spacing={1} direction="row" alignItems="center">
67
+ <Stack
68
+ spacing={1}
69
+ direction="row"
70
+ sx={{
71
+ alignItems: 'center',
72
+ }}>
68
73
  <InfoCard
69
74
  name={to.name}
70
75
  description={formatProductPrice(to as any, defaultCurrency, locale)}
@@ -91,7 +96,7 @@ export default function ProductCrossSell({ data, onChange }: { data: TProductExp
91
96
  return (
92
97
  <ProductsProvider>
93
98
  <Grid container>
94
- <Grid item xs={12}>
99
+ <Grid size={{ xs: 12 }}>
95
100
  <CrossSellForm data={data} onChange={onChange} />
96
101
  </Grid>
97
102
  </Grid>
@@ -10,19 +10,28 @@ import { FormProvider, useForm } from 'react-hook-form';
10
10
 
11
11
  import { getPricingModel } from '../../libs/util';
12
12
  import PriceForm, { DEFAULT_PRICE, Price } from '../price/form';
13
+ import { ProductsProvider } from '../../contexts/products';
13
14
 
14
- export default function EditPrice({
15
- price,
16
- loading,
17
- onSave,
18
- onCancel,
19
- }: {
15
+ interface EditPriceProps {
20
16
  price: Price;
21
17
  loading: boolean;
22
18
  onSave: EventHandler<any>;
23
19
  onCancel: EventHandler<any>;
24
- }) {
20
+ productType?: string;
21
+ }
22
+
23
+ export default function EditPrice({ price, loading, onSave, onCancel, productType = '' }: EditPriceProps) {
25
24
  const { t } = useLocaleContext();
25
+
26
+ // 处理 metadata 的回填逻辑
27
+ const processMetadata = (metadata: any) => {
28
+ if (isEmpty(metadata)) {
29
+ return {};
30
+ }
31
+
32
+ return metadata;
33
+ };
34
+
26
35
  const methods = useForm<Price>({
27
36
  mode: 'onChange',
28
37
  defaultValues: {
@@ -30,10 +39,8 @@ export default function EditPrice({
30
39
  unit_amount: fromUnitToToken(price.unit_amount, price.currency?.decimal),
31
40
  // @ts-ignore
32
41
  model: getPricingModel(price as any),
33
- metadata: isEmpty(price.metadata)
34
- ? []
35
- : // @ts-ignore
36
- Object.keys(price.metadata).map((x) => ({ key: x, value: price.metadata[x] })),
42
+ // @ts-ignore
43
+ metadata: processMetadata(price.metadata),
37
44
  recurring: price.recurring
38
45
  ? {
39
46
  ...price.recurring,
@@ -90,7 +97,9 @@ export default function EditPrice({
90
97
  </Stack>
91
98
  }>
92
99
  <FormProvider {...methods}>
93
- <PriceForm />
100
+ <ProductsProvider>
101
+ <PriceForm productType={productType || price.product?.type} />
102
+ </ProductsProvider>
94
103
  </FormProvider>
95
104
  </Dialog>
96
105
  );
@@ -15,29 +15,49 @@ export default function MetadataForm() {
15
15
  <Box sx={{ width: 1 }}>
16
16
  <Typography>{t('admin.product.features.label')}</Typography>
17
17
  {features.fields.map((feature, index) => (
18
- <Box key={feature.id} mt={2} sx={{ width: 1 }}>
18
+ <Box
19
+ key={feature.id}
20
+ sx={{ width: 1, gap: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between', mt: 2 }}>
19
21
  <Controller
20
22
  render={({ field }) => (
21
23
  <TextField
22
24
  {...field}
23
- sx={{ width: '80%' }}
25
+ sx={{ flex: 1, width: 'calc(100% - 60px)' }}
24
26
  size="small"
25
- inputProps={{ maxLength: 64 }}
26
27
  error={!!get(errors, field.name)}
27
28
  helperText={get(errors, field.name)?.message as string}
29
+ slotProps={{
30
+ htmlInput: { maxLength: 64 },
31
+ }}
28
32
  />
29
33
  )}
30
34
  rules={{ maxLength: { value: 64, message: t('common.maxLength', { len: 64 }) } }}
31
35
  name={`features.${index}.name`}
32
36
  control={control}
33
37
  />
34
- <IconButton size="small" sx={{ ml: 1 }} onClick={() => features.remove(index)}>
38
+ <IconButton
39
+ size="small"
40
+ onClick={() => features.remove(index)}
41
+ sx={{
42
+ border: '1px solid',
43
+ borderColor: 'grey.100',
44
+ borderRadius: 1,
45
+ padding: '8px',
46
+ }}>
35
47
  <DeleteOutlineOutlined color="error" sx={{ opacity: 0.75 }} />
36
48
  </IconButton>
37
49
  </Box>
38
50
  ))}
39
- <Box mt={features.fields.length ? 2 : 1}>
40
- <Button size="small" variant="outlined" color="inherit" onClick={() => features.append({ name: '' })}>
51
+ <Box
52
+ sx={{
53
+ mt: features.fields.length ? 2 : 1,
54
+ }}>
55
+ <Button
56
+ size="small"
57
+ variant="outlined"
58
+ color="primary"
59
+ sx={{ color: 'text.primary' }}
60
+ onClick={() => features.append({ name: '' })}>
41
61
  <AddOutlined fontSize="small" /> {t('admin.product.features.add')}
42
62
  </Button>
43
63
  </Box>