payment-kit 1.20.22 → 1.21.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.
@@ -234,23 +234,25 @@ export class SubscriptionRenewFailedEmailTemplate
234
234
  text: productName,
235
235
  },
236
236
  },
237
- ...(payer && [
238
- {
239
- type: 'text',
240
- data: {
241
- type: 'plain',
242
- color: '#9397A1',
243
- text: translate('notification.common.payer', locale),
244
- },
245
- },
246
- {
247
- type: 'text',
248
- data: {
249
- type: 'plain',
250
- text: payer,
251
- },
252
- },
253
- ]),
237
+ ...(payer
238
+ ? [
239
+ {
240
+ type: 'text',
241
+ data: {
242
+ type: 'plain',
243
+ color: '#9397A1',
244
+ text: translate('notification.common.payer', locale),
245
+ },
246
+ },
247
+ {
248
+ type: 'text',
249
+ data: {
250
+ type: 'plain',
251
+ text: payer,
252
+ },
253
+ },
254
+ ]
255
+ : []),
254
256
  {
255
257
  type: 'text',
256
258
  data: {
@@ -390,23 +390,25 @@ export class SubscriptionWillRenewEmailTemplate extends BaseSubscriptionEmailTem
390
390
  type: 'section',
391
391
  fields: [
392
392
  ...commonFields,
393
- ...(payer && [
394
- {
395
- type: 'text',
396
- data: {
397
- type: 'plain',
398
- color: '#9397A1',
399
- text: translate('notification.common.payer', locale),
400
- },
401
- },
402
- {
403
- type: 'text',
404
- data: {
405
- type: 'plain',
406
- text: payer,
407
- },
408
- },
409
- ]),
393
+ ...(payer
394
+ ? [
395
+ {
396
+ type: 'text',
397
+ data: {
398
+ type: 'plain',
399
+ color: '#9397A1',
400
+ text: translate('notification.common.payer', locale),
401
+ },
402
+ },
403
+ {
404
+ type: 'text',
405
+ data: {
406
+ type: 'plain',
407
+ text: payer,
408
+ },
409
+ },
410
+ ]
411
+ : []),
410
412
  ...renewAmountFields,
411
413
  ...balanceFields,
412
414
  ...insufficientBalanceFields,
@@ -17,10 +17,11 @@ import {
17
17
  } from './types';
18
18
  import { formatVendorUrl } from './util';
19
19
 
20
- const doRequestVendorData = (vendor: ProductVendor, orderId: string, url: string) => {
20
+ const doRequestVendorData = (vendor: ProductVendor, orderId: string, url: string, options: { shortUrl: boolean }) => {
21
21
  const { headers } = VendorAuth.signRequestWithHeaders({});
22
22
  const name = vendor?.name;
23
23
  const key = vendor?.vendor_key;
24
+ const { shortUrl } = options;
24
25
 
25
26
  return fetch(url, { headers })
26
27
  .then(async (r) => {
@@ -38,7 +39,7 @@ const doRequestVendorData = (vendor: ProductVendor, orderId: string, url: string
38
39
  `vendor status fetch failed, vendor: ${vendor.vendor_key}, orderId: ${orderId}, status: ${r.status}, url: ${url}`
39
40
  );
40
41
  }
41
- if (!data.dashboardUrl) {
42
+ if (!shortUrl || !data.dashboardUrl) {
42
43
  return {
43
44
  ...data,
44
45
  name,
@@ -255,15 +256,15 @@ export class LauncherAdapter implements VendorAdapter {
255
256
  }
256
257
  }
257
258
 
258
- getOrderStatus(vendor: ProductVendor, orderId: string): Promise<any> {
259
+ getOrderStatus(vendor: ProductVendor, orderId: string, options: { shortUrl: boolean }): Promise<any> {
259
260
  const url = formatVendorUrl(vendor, `/api/vendor/status/${orderId}`);
260
261
 
261
- return doRequestVendorData(vendor, orderId, url);
262
+ return doRequestVendorData(vendor, orderId, url, options);
262
263
  }
263
264
 
264
- getOrder(vendor: ProductVendor, orderId: string): Promise<any> {
265
+ getOrder(vendor: ProductVendor, orderId: string, options: { shortUrl: boolean }): Promise<any> {
265
266
  const url = formatVendorUrl(vendor, `/api/vendor/orders/${orderId}`);
266
267
 
267
- return doRequestVendorData(vendor, orderId, url);
268
+ return doRequestVendorData(vendor, orderId, url, options);
268
269
  }
269
270
  }
@@ -87,6 +87,6 @@ export interface VendorAdapter {
87
87
  fulfillOrder(params: FulfillOrderParams): Promise<FulfillOrderResult>;
88
88
  requestReturn(params: ReturnRequestParams): Promise<ReturnRequestResult>;
89
89
  checkOrderStatus(params: CheckOrderStatusParams): Promise<CheckOrderStatusResult>;
90
- getOrder(vendor: ProductVendor, orderId: string): Promise<any>;
91
- getOrderStatus(vendor: ProductVendor, orderId: string): Promise<any>;
90
+ getOrder(vendor: ProductVendor, orderId: string, option?: Record<string, any>): Promise<any>;
91
+ getOrderStatus(vendor: ProductVendor, orderId: string, option?: Record<string, any>): Promise<any>;
92
92
  }
@@ -158,7 +158,7 @@ export class VendorFulfillmentService {
158
158
  destination,
159
159
  amount: result.commissionAmount,
160
160
  currency_id: checkoutSession.currency_id,
161
- customer_id: checkoutSession.customer_id || '',
161
+ customer_id: '',
162
162
  payment_intent_id: paymentIntentId,
163
163
  payment_method_id: paymentMethodId,
164
164
  status: paymentMethod?.type === 'stripe' ? 'deferred' : 'pending',
@@ -316,7 +316,12 @@ async function testVendorConnection(req: any, res: any) {
316
316
  }
317
317
  }
318
318
 
319
- async function executeVendorOperation(vendorId: string, orderId: string, operation: 'getOrder' | 'getOrderStatus') {
319
+ async function executeVendorOperation(
320
+ vendorId: string,
321
+ orderId: string,
322
+ operation: 'getOrder' | 'getOrderStatus',
323
+ shortUrl: boolean
324
+ ) {
320
325
  if (!vendorId || !orderId) {
321
326
  return {
322
327
  error: 'Bad Request',
@@ -336,7 +341,7 @@ async function executeVendorOperation(vendorId: string, orderId: string, operati
336
341
 
337
342
  try {
338
343
  const vendorAdapter = await VendorFulfillmentService.getVendorAdapter(vendor.vendor_key);
339
- const data = await vendorAdapter[operation](vendor, orderId);
344
+ const data = await vendorAdapter[operation](vendor, orderId, { shortUrl });
340
345
 
341
346
  return { data: { ...data, vendorType: vendor.vendor_type }, code: 200 };
342
347
  } catch (error: any) {
@@ -355,7 +360,7 @@ async function executeVendorOperation(vendorId: string, orderId: string, operati
355
360
  }
356
361
  }
357
362
 
358
- async function processVendorOrders(sessionId: string, operation: 'getOrder' | 'getOrderStatus') {
363
+ async function processVendorOrders(sessionId: string, operation: 'getOrder' | 'getOrderStatus', shortUrl: boolean) {
359
364
  const doc = await CheckoutSession.findByPk(sessionId);
360
365
 
361
366
  if (!doc) {
@@ -397,7 +402,7 @@ async function processVendorOrders(sessionId: string, operation: 'getOrder' | 'g
397
402
  };
398
403
  }
399
404
 
400
- const result = await executeVendorOperation(item.vendor_id, item.order_id, operation);
405
+ const result = await executeVendorOperation(item.vendor_id, item.order_id, operation, shortUrl);
401
406
 
402
407
  // Handle error responses from vendor functions
403
408
  if (result.error) {
@@ -431,7 +436,7 @@ async function processVendorOrders(sessionId: string, operation: 'getOrder' | 'g
431
436
  }
432
437
 
433
438
  async function getVendorStatus(sessionId: string) {
434
- const result: any = await processVendorOrders(sessionId, 'getOrderStatus');
439
+ const result: any = await processVendorOrders(sessionId, 'getOrderStatus', false);
435
440
 
436
441
  if (result.subscriptionId) {
437
442
  const subscriptionUrl = getUrl(`/customer/subscription/${result.subscriptionId}`);
@@ -463,9 +468,10 @@ async function getVendorFulfillmentStatus(req: any, res: any) {
463
468
 
464
469
  async function getVendorFulfillmentDetail(req: any, res: any) {
465
470
  const { sessionId } = req.params;
471
+ const { shortUrl } = req.query;
466
472
 
467
473
  try {
468
- const detail = await processVendorOrders(sessionId, 'getOrder');
474
+ const detail = await processVendorOrders(sessionId, 'getOrder', shortUrl === 'true');
469
475
  if (detail.code) {
470
476
  return res.status(detail.code).json({ error: detail.error });
471
477
  }
@@ -505,7 +511,7 @@ async function redirectToVendor(req: any, res: any) {
505
511
  return res.redirect('/404');
506
512
  }
507
513
 
508
- const result = await executeVendorOperation(vendorId, order.order_id || '', 'getOrder');
514
+ const result = await executeVendorOperation(vendorId, order.order_id || '', 'getOrder', false);
509
515
  if (result.error || !result.data) {
510
516
  logger.warn('Vendor status detail not found', {
511
517
  subscriptionId,
package/blocklet.yml CHANGED
@@ -14,7 +14,7 @@ repository:
14
14
  type: git
15
15
  url: git+https://github.com/blocklet/payment-kit.git
16
16
  specVersion: 1.2.8
17
- version: 1.20.22
17
+ version: 1.21.0
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.20.22",
3
+ "version": "1.21.0",
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,33 +45,33 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@abtnode/cron": "^1.16.52-beta-20250912-112002-e3499e9c",
48
- "@arcblock/did": "^1.25.3",
49
- "@arcblock/did-connect-react": "^3.1.41",
48
+ "@arcblock/did": "^1.25.4",
49
+ "@arcblock/did-connect-react": "^3.1.43",
50
50
  "@arcblock/did-connect-storage-nedb": "^1.8.0",
51
- "@arcblock/did-util": "^1.25.3",
52
- "@arcblock/jwt": "^1.25.3",
53
- "@arcblock/ux": "^3.1.41",
54
- "@arcblock/validator": "^1.25.3",
51
+ "@arcblock/did-util": "^1.25.4",
52
+ "@arcblock/jwt": "^1.25.4",
53
+ "@arcblock/ux": "^3.1.43",
54
+ "@arcblock/validator": "^1.25.4",
55
55
  "@blocklet/did-space-js": "^1.1.27",
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-broker-client": "1.20.22",
60
- "@blocklet/payment-react": "1.20.22",
61
- "@blocklet/payment-vendor": "1.20.22",
59
+ "@blocklet/payment-broker-client": "1.21.0",
60
+ "@blocklet/payment-react": "1.21.0",
61
+ "@blocklet/payment-vendor": "1.21.0",
62
62
  "@blocklet/sdk": "^1.16.52-beta-20250912-112002-e3499e9c",
63
- "@blocklet/ui-react": "^3.1.41",
63
+ "@blocklet/ui-react": "^3.1.43",
64
64
  "@blocklet/uploader": "^0.2.12",
65
- "@blocklet/xss": "^0.2.7",
65
+ "@blocklet/xss": "^0.2.8",
66
66
  "@mui/icons-material": "^7.1.2",
67
67
  "@mui/lab": "7.0.0-beta.14",
68
68
  "@mui/material": "^7.1.2",
69
69
  "@mui/system": "^7.1.1",
70
- "@ocap/asset": "^1.25.3",
71
- "@ocap/client": "^1.25.3",
72
- "@ocap/mcrypto": "^1.25.3",
73
- "@ocap/util": "^1.25.3",
74
- "@ocap/wallet": "^1.25.3",
70
+ "@ocap/asset": "^1.25.4",
71
+ "@ocap/client": "^1.25.4",
72
+ "@ocap/mcrypto": "^1.25.4",
73
+ "@ocap/util": "^1.25.4",
74
+ "@ocap/wallet": "^1.25.4",
75
75
  "@stripe/react-stripe-js": "^2.9.0",
76
76
  "@stripe/stripe-js": "^2.4.0",
77
77
  "ahooks": "^3.8.5",
@@ -128,7 +128,7 @@
128
128
  "devDependencies": {
129
129
  "@abtnode/types": "^1.16.52-beta-20250912-112002-e3499e9c",
130
130
  "@arcblock/eslint-config-ts": "^0.3.3",
131
- "@blocklet/payment-types": "1.20.22",
131
+ "@blocklet/payment-types": "1.21.0",
132
132
  "@types/cookie-parser": "^1.4.9",
133
133
  "@types/cors": "^2.8.19",
134
134
  "@types/debug": "^4.1.12",
@@ -175,5 +175,5 @@
175
175
  "parser": "typescript"
176
176
  }
177
177
  },
178
- "gitHead": "a0a691de0cd6e47efbc0e4de01d200bb9193c435"
178
+ "gitHead": "d133aa0e8c6681dd77bafe024c1e1dafb1addf0d"
179
179
  }
@@ -94,6 +94,7 @@ export default flat({
94
94
  promotional: 'Promotional',
95
95
  viewInvoice: 'View Invoice',
96
96
  viewSourceData: 'View Source',
97
+ goToConfigure: 'Go to Configure',
97
98
  },
98
99
  notification: {
99
100
  preferences: {
@@ -1685,6 +1686,41 @@ export default flat({
1685
1686
  title: 'Pricing Tables',
1686
1687
  intro: 'Create beautiful pricing tables for your products',
1687
1688
  },
1689
+ metering: {
1690
+ title: 'Usage Metering',
1691
+ intro: 'Track usage with meters and sell credits as top-up packages',
1692
+ dialog: {
1693
+ title: 'How to set up credit-based metering',
1694
+ description:
1695
+ 'Credit-based metering allows you to track usage and charge customers based on consumption. Follow these steps to get started:',
1696
+ steps: {
1697
+ step1: {
1698
+ title: 'Create a meter to track your usage events',
1699
+ description:
1700
+ 'Set up meters to monitor API calls, data usage, or any measurable activity in your application.',
1701
+ },
1702
+ step2: {
1703
+ title: 'Create Credit top-up products and pricing',
1704
+ description:
1705
+ 'Define credit packages that customers can purchase, with flexible pricing models and validity periods.',
1706
+ },
1707
+ step3: {
1708
+ title: 'Create a payment link and share it so users can buy Credits',
1709
+ description: 'Generate shareable payment links that allow customers to easily purchase credit packages.',
1710
+ },
1711
+ step4: {
1712
+ title: 'Integrate and report Credit usage',
1713
+ description:
1714
+ 'Use our SDK to report usage events and automatically deduct credits from customer balances.',
1715
+ },
1716
+ },
1717
+ docText: 'Read the credit billing guide',
1718
+ },
1719
+ },
1720
+ promotions: {
1721
+ title: 'Promotions',
1722
+ intro: 'Create coupons and promotion codes to drive conversions',
1723
+ },
1688
1724
  donate: {
1689
1725
  title: 'Donation',
1690
1726
  intro: 'Add donation button to your application with <CheckoutDonate />',
@@ -1699,5 +1735,6 @@ export default flat({
1699
1735
  link: 'https://www.npmjs.com/package/@blocklet/payment-js',
1700
1736
  },
1701
1737
  },
1738
+ viewDocs: 'View docs',
1702
1739
  },
1703
1740
  });
@@ -93,6 +93,7 @@ export default flat({
93
93
  promotional: '促销',
94
94
  viewInvoice: '查看账单',
95
95
  viewSourceData: '查看来源',
96
+ goToConfigure: '前往配置',
96
97
  },
97
98
  notification: {
98
99
  preferences: {
@@ -1632,6 +1633,37 @@ export default flat({
1632
1633
  title: '定价表',
1633
1634
  intro: '为您的产品创建美观的定价表',
1634
1635
  },
1636
+ metering: {
1637
+ title: '计量与额度',
1638
+ intro: '使用计量器跟踪用量,并通过额度套餐售卖 Credits',
1639
+ dialog: {
1640
+ title: '如何开启 Credit 计费',
1641
+ description: 'Credit 计费模式让您可以跟踪用量并基于消费向客户收费。按照以下步骤开始配置:',
1642
+ steps: {
1643
+ step1: {
1644
+ title: '创建计量器用于跟踪用量事件',
1645
+ description: '设置计量器来监控 API 调用、数据使用量或应用中任何可测量的活动。',
1646
+ },
1647
+ step2: {
1648
+ title: '创建 Credits 购买套餐和定价',
1649
+ description: '定义客户可以购买的额度套餐,支持灵活的定价模式和有效期设置。',
1650
+ },
1651
+ step3: {
1652
+ title: '创建支付链接并分享给用户',
1653
+ description: '生成可分享的支付链接,让客户轻松购买额度套餐。',
1654
+ },
1655
+ step4: {
1656
+ title: '集成并上报 Credits 用量',
1657
+ description: '使用我们的 SDK 上报使用事件,自动从客户余额中扣除额度。',
1658
+ },
1659
+ },
1660
+ docText: '查看 Credit 计费指南',
1661
+ },
1662
+ },
1663
+ promotions: {
1664
+ title: '促销与优惠',
1665
+ intro: '创建优惠券与促销码,提升转化率',
1666
+ },
1635
1667
  donate: {
1636
1668
  title: '打赏功能',
1637
1669
  intro: '使用 <CheckoutDonate /> 组件为应用添加打赏按钮',
@@ -1646,5 +1678,6 @@ export default flat({
1646
1678
  link: 'https://www.npmjs.com/package/@blocklet/payment-js',
1647
1679
  },
1648
1680
  },
1681
+ viewDocs: '查看文档',
1649
1682
  },
1650
1683
  });
@@ -1,5 +1,6 @@
1
1
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
- import { Box, Grid, Typography, Divider } from '@mui/material';
2
+ import { Box, Grid, Typography, Divider, Button, Link, Tooltip } from '@mui/material';
3
+ import Dialog from '@arcblock/ux/lib/Dialog';
3
4
  import { useNavigate } from 'react-router-dom';
4
5
  import {
5
6
  Link as LinkIcon,
@@ -8,7 +9,11 @@ import {
8
9
  Inventory2Outlined,
9
10
  TableChartOutlined,
10
11
  FavoriteBorderOutlined,
12
+ Speed,
13
+ LocalOfferOutlined,
14
+ HelpOutline,
11
15
  } from '@mui/icons-material';
16
+ import { useState } from 'react';
12
17
 
13
18
  const basicFeatures = [
14
19
  {
@@ -32,6 +37,24 @@ const basicFeatures = [
32
37
  ];
33
38
 
34
39
  const advancedFeatures = [
40
+ {
41
+ title: 'integrations.features.metering.title',
42
+ description: 'integrations.features.metering.intro',
43
+ icon: <Speed sx={{ fontSize: 32, color: 'text.lighter' }} />,
44
+ dialog: 'metering',
45
+ doc: {
46
+ url: 'https://www.arcblock.io/blog/en/payment-kit-enhanced-credit-management',
47
+ },
48
+ },
49
+ {
50
+ title: 'integrations.features.promotions.title',
51
+ description: 'integrations.features.promotions.intro',
52
+ icon: <LocalOfferOutlined sx={{ fontSize: 32, color: 'text.lighter' }} />,
53
+ path: '/admin/products/coupons',
54
+ doc: {
55
+ url: 'https://www.arcblock.io/content/blog/en/payment-kit-promotion-support',
56
+ },
57
+ },
35
58
  {
36
59
  title: 'integrations.features.donate.title',
37
60
  description: 'integrations.features.donate.intro',
@@ -48,7 +71,7 @@ const advancedFeatures = [
48
71
  title: 'integrations.features.api.title',
49
72
  description: 'integrations.features.api.intro',
50
73
  icon: <Code sx={{ fontSize: 32, color: 'text.lighter' }} />,
51
- path: 'https://www.arcblock.io/docs/arcblock-payment-kit/en/start-payment-js',
74
+ path: 'https://www.staging.arcblock.io/content/docs/payment-kit-sdk',
52
75
  external: true,
53
76
  },
54
77
  ];
@@ -56,8 +79,13 @@ const advancedFeatures = [
56
79
  export default function Overview() {
57
80
  const { t } = useLocaleContext();
58
81
  const navigate = useNavigate();
82
+ const [openMeterDialog, setOpenMeterDialog] = useState(false);
59
83
 
60
84
  const handleClick = (item: any) => {
85
+ if (item.dialog === 'metering') {
86
+ setOpenMeterDialog(true);
87
+ return;
88
+ }
61
89
  if (item.external) {
62
90
  window.open(item.path, '_blank');
63
91
  } else {
@@ -179,13 +207,37 @@ export default function Overview() {
179
207
  mb: 1,
180
208
  }}>
181
209
  {item.icon}
182
- <Typography
183
- variant="h4"
210
+ <Box
184
211
  sx={{
212
+ display: 'flex',
213
+ alignItems: 'center',
185
214
  mt: 1.5,
186
215
  }}>
187
- {t(item.title)}
188
- </Typography>
216
+ <Typography variant="h4">{t(item.title)}</Typography>
217
+ {item.doc?.url && (
218
+ <Tooltip title={t('integrations.viewDocs')} placement="top">
219
+ <Link
220
+ href={item.doc.url}
221
+ target="_blank"
222
+ rel="noopener"
223
+ onClick={(e) => e.stopPropagation()}
224
+ sx={{
225
+ display: 'flex',
226
+ alignItems: 'center',
227
+ ml: 1,
228
+ color: 'text.secondary',
229
+ '&:hover': { color: 'primary.main' },
230
+ }}>
231
+ <HelpOutline
232
+ fontSize="small"
233
+ sx={{
234
+ cursor: 'pointer',
235
+ }}
236
+ />
237
+ </Link>
238
+ </Tooltip>
239
+ )}
240
+ </Box>
189
241
  </Box>
190
242
  <Typography
191
243
  variant="body2"
@@ -198,6 +250,96 @@ export default function Overview() {
198
250
  </Grid>
199
251
  ))}
200
252
  </Grid>
253
+
254
+ <Dialog
255
+ open={openMeterDialog}
256
+ onClose={() => setOpenMeterDialog(false)}
257
+ maxWidth="sm"
258
+ fullWidth
259
+ className="base-dialog"
260
+ title={t('integrations.features.metering.dialog.title')}
261
+ actions={
262
+ <Box sx={{ display: 'flex', gap: 2 }}>
263
+ <Button onClick={() => setOpenMeterDialog(false)}>{t('common.cancel')}</Button>
264
+ <Button onClick={() => navigate('/admin/meters')} variant="contained">
265
+ {t('common.goToConfigure')}
266
+ </Button>
267
+ </Box>
268
+ }>
269
+ <Typography variant="body2" sx={{ color: 'text.secondary', mb: 3 }}>
270
+ {t('integrations.features.metering.dialog.description')}
271
+ </Typography>
272
+
273
+ <Box
274
+ component="ol"
275
+ sx={{
276
+ pl: 0,
277
+ mb: 3,
278
+ listStyle: 'none',
279
+ counterReset: 'step-counter',
280
+ }}>
281
+ {['step1', 'step2', 'step3', 'step4'].map((stepKey) => (
282
+ <Box
283
+ key={stepKey}
284
+ component="li"
285
+ sx={{
286
+ mb: 3,
287
+ pl: 3,
288
+ position: 'relative',
289
+ counterIncrement: 'step-counter',
290
+ '&::before': {
291
+ content: 'counter(step-counter)',
292
+ position: 'absolute',
293
+ left: 0,
294
+ top: 0,
295
+ width: 24,
296
+ height: 24,
297
+ borderRadius: '50%',
298
+ backgroundColor: 'primary.main',
299
+ color: 'white',
300
+ fontSize: '12px',
301
+ fontWeight: 'bold',
302
+ display: 'flex',
303
+ alignItems: 'center',
304
+ justifyContent: 'center',
305
+ },
306
+ }}>
307
+ <Box sx={{ ml: 0.5 }}>
308
+ <Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 0.5 }}>
309
+ {t(`integrations.features.metering.dialog.steps.${stepKey}.title`)}
310
+ </Typography>
311
+ <Typography variant="body2" sx={{ color: 'text.secondary', lineHeight: 1.5 }}>
312
+ {t(`integrations.features.metering.dialog.steps.${stepKey}.description`)}
313
+ </Typography>
314
+ </Box>
315
+ </Box>
316
+ ))}
317
+ </Box>
318
+
319
+ <Box
320
+ sx={{
321
+ p: 2,
322
+ backgroundColor: 'action.hover',
323
+ borderRadius: 1,
324
+ border: '1px solid',
325
+ borderColor: 'divider',
326
+ }}>
327
+ <Link
328
+ href="https://www.staging.arcblock.io/content/docs/payment-kit-sdk/en/payment-kit-sdk-core-concepts-credit-billing"
329
+ target="_blank"
330
+ rel="noopener"
331
+ underline="hover"
332
+ sx={{
333
+ color: 'primary.main',
334
+ fontWeight: 500,
335
+ display: 'flex',
336
+ alignItems: 'center',
337
+ gap: 0.5,
338
+ }}>
339
+ {t('integrations.features.metering.dialog.docText')}
340
+ </Link>
341
+ </Box>
342
+ </Dialog>
201
343
  </Box>
202
344
  );
203
345
  }