@blocklet/payment-react 1.23.11 → 1.24.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.
@@ -7,7 +7,7 @@ import Toast from "@arcblock/ux/lib/Toast";
7
7
  import { joinURL } from "ufo";
8
8
  import { useRequest } from "ahooks";
9
9
  import pWaitFor from "p-wait-for";
10
- import { Dialog } from "@arcblock/ux";
10
+ import Dialog from "@arcblock/ux/lib/Dialog/dialog";
11
11
  import { CheckCircle as CheckCircleIcon } from "@mui/icons-material";
12
12
  import debounce from "lodash/debounce";
13
13
  import { usePaymentContext } from "../contexts/payment.js";
@@ -5,7 +5,7 @@ import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
5
5
  import Toast from "@arcblock/ux/lib/Toast";
6
6
  import { joinURL } from "ufo";
7
7
  import { useRequest } from "ahooks";
8
- import { Dialog } from "@arcblock/ux";
8
+ import Dialog from "@arcblock/ux/lib/Dialog/dialog";
9
9
  import { usePaymentContext } from "../contexts/payment.js";
10
10
  import { formatError, formatToDate, getPrefix, isCrossOrigin } from "../libs/util.js";
11
11
  import api from "../libs/api.js";
@@ -1,7 +1,7 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
3
3
  import Toast from "@arcblock/ux/lib/Toast";
4
- import { Dialog } from "@arcblock/ux";
4
+ import Dialog from "@arcblock/ux/lib/Dialog/dialog";
5
5
  import { Button, Typography } from "@mui/material";
6
6
  import { useSetState } from "ahooks";
7
7
  import { useEffect, useRef } from "react";
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
3
- import { Box, Typography, Grid, Stack, Link, Button } from "@mui/material";
3
+ import { Box, Typography, Grid, Stack, Link, Button, Chip } from "@mui/material";
4
4
  import { useRequest } from "ahooks";
5
5
  import { useNavigate } from "react-router-dom";
6
6
  import React, { useCallback, useEffect, useRef, useState } from "react";
@@ -130,6 +130,7 @@ const TransactionsTable = React.memo((props) => {
130
130
  customBodyRenderLite: (_, index) => {
131
131
  const item = data?.list[index];
132
132
  const isGrant = item.activity_type === "grant";
133
+ const isExpiredGrant = isGrant && item.status === "expired";
133
134
  const amount = isGrant ? item.amount : item.credit_amount;
134
135
  const currency = item.paymentCurrency || item.currency;
135
136
  const unit = !isGrant && item.meter?.unit ? item.meter.unit : currency?.symbol;
@@ -137,11 +138,11 @@ const TransactionsTable = React.memo((props) => {
137
138
  if (!includeGrants) {
138
139
  return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleTransactionClick(e, item), children: /* @__PURE__ */ jsx(Typography, { children: displayAmount }) });
139
140
  }
140
- return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleTransactionClick(e, item), children: /* @__PURE__ */ jsxs(
141
+ const amountNode = /* @__PURE__ */ jsxs(
141
142
  Typography,
142
143
  {
143
144
  sx: {
144
- color: isGrant ? "success.main" : "error.main"
145
+ color: isGrant ? isExpiredGrant ? "text.disabled" : "success.main" : "error.main"
145
146
  },
146
147
  children: [
147
148
  isGrant ? "+" : "-",
@@ -149,7 +150,25 @@ const TransactionsTable = React.memo((props) => {
149
150
  displayAmount
150
151
  ]
151
152
  }
152
- ) });
153
+ );
154
+ return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleTransactionClick(e, item), children: /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", justifyContent: "flex-end", sx: { width: "100%" }, children: [
155
+ isExpiredGrant && /* @__PURE__ */ jsx(
156
+ Chip,
157
+ {
158
+ label: t("admin.creditGrants.status.expired"),
159
+ size: "small",
160
+ variant: "outlined",
161
+ sx: {
162
+ mr: 2,
163
+ height: 18,
164
+ fontSize: "12px",
165
+ color: "text.disabled",
166
+ borderColor: "text.disabled"
167
+ }
168
+ }
169
+ ),
170
+ amountNode
171
+ ] }) });
153
172
  }
154
173
  }
155
174
  },
@@ -160,8 +179,10 @@ const TransactionsTable = React.memo((props) => {
160
179
  customBodyRenderLite: (_, index) => {
161
180
  const item = data?.list[index];
162
181
  const isGrant = item.activity_type === "grant";
182
+ const isExpiredGrant = isGrant && item.status === "expired";
163
183
  const grantName = isGrant ? item.name : item.creditGrant.name;
164
184
  const grantId = isGrant ? item.id : item.credit_grant_id;
185
+ const nameNode = /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { cursor: "pointer", color: isExpiredGrant ? "text.disabled" : void 0 }, children: grantName || `Grant ${grantId.slice(-6)}` });
165
186
  return /* @__PURE__ */ jsx(
166
187
  Stack,
167
188
  {
@@ -174,7 +195,7 @@ const TransactionsTable = React.memo((props) => {
174
195
  sx: {
175
196
  alignItems: "center"
176
197
  },
177
- children: /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { cursor: "pointer" }, children: grantName || `Grant ${grantId.slice(-6)}` })
198
+ children: nameNode
178
199
  }
179
200
  );
180
201
  }
@@ -187,8 +208,10 @@ const TransactionsTable = React.memo((props) => {
187
208
  customBodyRenderLite: (_, index) => {
188
209
  const item = data?.list[index];
189
210
  const isGrant = item.activity_type === "grant";
211
+ const isExpiredGrant = isGrant && item.status === "expired";
190
212
  const description = isGrant ? item.name || item.description || "Credit Granted" : item.subscription?.description || item.description || `${item.meter_event_name} usage`;
191
- return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleTransactionClick(e, item), children: /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontWeight: 400 }, children: description }) });
213
+ const descriptionNode = /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontWeight: 400, color: isExpiredGrant ? "text.disabled" : void 0 }, children: description });
214
+ return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleTransactionClick(e, item), children: descriptionNode });
192
215
  }
193
216
  }
194
217
  },
package/es/locales/en.js CHANGED
@@ -264,6 +264,10 @@ export default flat({
264
264
  oneTimeEnoughWithExpiry: "You have a usage overage of {amount}. This purchase adds {totalAmount} (valid for {duration} {unit}). After covering the overage, your new available balance will be {availableAmount}.",
265
265
  recurringEnough: "You have a usage overage of {amount}. This subscription adds {totalAmount} {period}. After covering the overage, your new available balance will be {availableAmount}.",
266
266
  recurringEnoughWithExpiry: "You have a usage overage of {amount}. This subscription adds {totalAmount} {period} (valid for {duration} {unit}). After covering the overage, your new available balance will be {availableAmount}."
267
+ },
268
+ schedule: {
269
+ periodic: "Grant {amount} every {interval}.",
270
+ withRefresh: "Grant {amount} every {interval}; unused credits expire with the next grant."
267
271
  }
268
272
  },
269
273
  expired: {
package/es/locales/zh.js CHANGED
@@ -301,6 +301,10 @@ export default flat({
301
301
  oneTimeEnoughWithExpiry: "\u60A8\u6709 {amount} \u7684\u4F7F\u7528\u8D85\u989D\u3002\u672C\u6B21\u8D2D\u4E70\u5C06\u589E\u52A0 {totalAmount}\uFF08\u6709\u6548\u671F {duration} {unit}\uFF09\u3002\u6263\u9664\u8D85\u989D\u540E\uFF0C\u60A8\u7684\u65B0\u53EF\u7528\u4F59\u989D\u5C06\u4E3A {availableAmount}\u3002",
302
302
  recurringEnough: "\u60A8\u6709 {amount} \u7684\u4F7F\u7528\u8D85\u989D\u3002\u672C\u8BA2\u9605{period}\u5C06\u589E\u52A0 {totalAmount}\u3002\u6263\u9664\u8D85\u989D\u540E\uFF0C\u60A8\u7684\u65B0\u53EF\u7528\u4F59\u989D\u5C06\u4E3A {availableAmount}\u3002",
303
303
  recurringEnoughWithExpiry: "\u60A8\u6709 {amount} \u7684\u4F7F\u7528\u8D85\u989D\u3002\u672C\u8BA2\u9605{period}\u5C06\u589E\u52A0 {totalAmount}\uFF08\u6709\u6548\u671F {duration} {unit}\uFF09\u3002\u6263\u9664\u8D85\u989D\u540E\uFF0C\u60A8\u7684\u65B0\u53EF\u7528\u4F59\u989D\u5C06\u4E3A {availableAmount}\u3002"
304
+ },
305
+ schedule: {
306
+ periodic: "\u6BCF{interval}\u53D1\u653E {amount}\u3002",
307
+ withRefresh: "\u6BCF{interval}\u53D1\u653E {amount}\uFF0C\u672A\u4F7F\u7528\u989D\u5EA6\u5C06\u5728\u4E0B\u6B21\u53D1\u653E\u65F6\u8FC7\u671F\u3002"
304
308
  }
305
309
  },
306
310
  emptyItems: {
@@ -17,6 +17,7 @@ import {
17
17
  formatUpsellSaving,
18
18
  formatAmount,
19
19
  formatCreditForCheckout,
20
+ formatCreditAmount,
20
21
  formatBNStr
21
22
  } from "../libs/util.js";
22
23
  import ProductCard from "./product-card.js";
@@ -69,11 +70,13 @@ export default function ProductItem({
69
70
  const saving = formatUpsellSaving(items, currency);
70
71
  const metered = item.price?.recurring?.usage_type === "metered" ? t("common.metered") : "";
71
72
  const canUpsell = mode === "normal" && items.length === 1;
72
- const isCreditProduct = item.price.product?.type === "credit" && item.price.metadata?.credit_config?.credit_amount;
73
- const creditAmount = isCreditProduct ? Number(item.price.metadata.credit_config.credit_amount) : 0;
74
- const creditCurrency = isCreditProduct ? findCurrency(settings.paymentMethods, item.price.metadata?.credit_config?.currency_id ?? "") : null;
73
+ const isCreditProduct = item.price.product?.type === "credit" && item.price.metadata?.credit_config;
74
+ const creditAmount = isCreditProduct && item.price.metadata?.credit_config?.credit_amount ? Number(item.price.metadata.credit_config.credit_amount) : 0;
75
+ const creditCurrency = isCreditProduct && item.price.metadata?.credit_config?.currency_id ? findCurrency(settings.paymentMethods, item.price.metadata.credit_config.currency_id) : null;
75
76
  const validDuration = item.price.metadata?.credit_config?.valid_duration_value;
76
77
  const validDurationUnit = item.price.metadata?.credit_config?.valid_duration_unit || "days";
78
+ const scheduleConfig = item.price.metadata?.credit_config?.schedule;
79
+ const hasSchedule = scheduleConfig?.enabled && scheduleConfig?.delivery_mode && scheduleConfig.delivery_mode !== "invoice";
77
80
  const userDid = session?.user?.did;
78
81
  const { data: pendingAmount } = useRequest(
79
82
  async () => {
@@ -254,6 +257,44 @@ export default function ProductItem({
254
257
  const type = isRecurring ? "recurringEnough" : "oneTimeEnough";
255
258
  return t(getLocaleKey("pending", type), buildPendingParams(pendingAmountBN, actualAvailable));
256
259
  };
260
+ const formatScheduleInfo = () => {
261
+ if (!hasSchedule || !scheduleConfig) return null;
262
+ const totalCredit = creditAmount * (localQuantity || 0);
263
+ const currencySymbol = creditCurrency?.symbol || "Credits";
264
+ const intervalUnit = scheduleConfig.interval_unit;
265
+ const intervalValue = scheduleConfig.interval_value;
266
+ let amountPerGrant;
267
+ if (scheduleConfig.amount_per_grant) {
268
+ amountPerGrant = Number(scheduleConfig.amount_per_grant) * (localQuantity || 1);
269
+ } else {
270
+ const billingIntervalUnit = item.price.recurring?.interval || "month";
271
+ const billingIntervalCount = item.price.recurring?.interval_count || 1;
272
+ const unitToHours = {
273
+ hour: 1,
274
+ day: 24,
275
+ week: 168,
276
+ month: 720
277
+ // ~30 days
278
+ };
279
+ const billingHours = unitToHours[billingIntervalUnit] * billingIntervalCount;
280
+ const scheduleHours = unitToHours[intervalUnit] * intervalValue;
281
+ const grantsPerPeriod = Math.floor(billingHours / scheduleHours);
282
+ amountPerGrant = grantsPerPeriod > 0 ? totalCredit / grantsPerPeriod : totalCredit;
283
+ }
284
+ const formattedAmount = formatCreditAmount(formatNumber(amountPerGrant), currencySymbol);
285
+ const intervalDisplay = intervalValue === 1 ? t(`common.${intervalUnit}`) : ` ${intervalValue} ${t(`common.${intervalUnit}s`)} `;
286
+ const expireWithNext = scheduleConfig.expire_with_next_grant;
287
+ if (expireWithNext) {
288
+ return t("payment.checkout.credit.schedule.withRefresh", {
289
+ amount: formattedAmount,
290
+ interval: intervalDisplay
291
+ });
292
+ }
293
+ return t("payment.checkout.credit.schedule.periodic", {
294
+ amount: formattedAmount,
295
+ interval: intervalDisplay
296
+ });
297
+ };
257
298
  const primaryText = useMemo(() => {
258
299
  const price = item.upsell_price || item.price || {};
259
300
  const isRecurring = price?.type === "recurring" && price?.recurring;
@@ -495,7 +536,7 @@ export default function ProductItem({
495
536
  ]
496
537
  }
497
538
  ) }),
498
- isCreditProduct && /* @__PURE__ */ jsx(Alert, { severity: "info", sx: { mt: 1, fontSize: "0.875rem" }, icon: false, children: formatCreditInfo() }),
539
+ isCreditProduct && /* @__PURE__ */ jsx(Alert, { severity: "info", sx: { mt: 1, fontSize: "0.875rem" }, icon: false, children: hasSchedule ? /* @__PURE__ */ jsx(Typography, { component: "span", sx: { fontSize: "inherit" }, children: formatScheduleInfo() }) : /* @__PURE__ */ jsx(Typography, { component: "span", sx: { fontSize: "inherit" }, children: formatCreditInfo() }) }),
499
540
  children
500
541
  ]
501
542
  }
@@ -5,7 +5,7 @@ import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
5
5
  import { Box, Grow, Link, Paper, Stack, styled, Typography } from "@mui/material";
6
6
  import { useEffect, useRef, useState } from "react";
7
7
  import { joinURL } from "ufo";
8
- import { Button } from "@arcblock/ux";
8
+ import Button from "@arcblock/ux/lib/Button";
9
9
  import { usePaymentContext } from "../contexts/payment.js";
10
10
  import { VendorPlaceholder, VendorProgressItem } from "./progress-item.js";
11
11
  export default function PaymentSuccess({
@@ -13,7 +13,7 @@ var _Toast = _interopRequireDefault(require("@arcblock/ux/lib/Toast"));
13
13
  var _ufo = require("ufo");
14
14
  var _ahooks = require("ahooks");
15
15
  var _pWaitFor = _interopRequireDefault(require("p-wait-for"));
16
- var _ux = require("@arcblock/ux");
16
+ var _dialog = _interopRequireDefault(require("@arcblock/ux/lib/Dialog/dialog"));
17
17
  var _iconsMaterial = require("@mui/icons-material");
18
18
  var _debounce = _interopRequireDefault(require("lodash/debounce"));
19
19
  var _payment = require("../contexts/payment");
@@ -457,7 +457,7 @@ function OverdueInvoicePayment({
457
457
  })
458
458
  });
459
459
  }
460
- return /* @__PURE__ */(0, _jsxRuntime.jsx)(_ux.Dialog, {
460
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_dialog.default, {
461
461
  PaperProps: {
462
462
  style: {
463
463
  minHeight: "auto"
@@ -11,7 +11,7 @@ var _context = require("@arcblock/ux/lib/Locale/context");
11
11
  var _Toast = _interopRequireDefault(require("@arcblock/ux/lib/Toast"));
12
12
  var _ufo = require("ufo");
13
13
  var _ahooks = require("ahooks");
14
- var _ux = require("@arcblock/ux");
14
+ var _dialog = _interopRequireDefault(require("@arcblock/ux/lib/Dialog/dialog"));
15
15
  var _payment = require("../contexts/payment");
16
16
  var _util = require("../libs/util");
17
17
  var _api = _interopRequireDefault(require("../libs/api"));
@@ -152,7 +152,7 @@ function RecoverSubscription({
152
152
  if (loading) {
153
153
  return null;
154
154
  }
155
- return /* @__PURE__ */(0, _jsxRuntime.jsx)(_ux.Dialog, {
155
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_dialog.default, {
156
156
  PaperProps: {
157
157
  style: {
158
158
  minHeight: "auto"
@@ -7,7 +7,7 @@ module.exports = StripePaymentAction;
7
7
  var _jsxRuntime = require("react/jsx-runtime");
8
8
  var _context = require("@arcblock/ux/lib/Locale/context");
9
9
  var _Toast = _interopRequireDefault(require("@arcblock/ux/lib/Toast"));
10
- var _ux = require("@arcblock/ux");
10
+ var _dialog = _interopRequireDefault(require("@arcblock/ux/lib/Dialog/dialog"));
11
11
  var _material = require("@mui/material");
12
12
  var _ahooks = require("ahooks");
13
13
  var _react = require("react");
@@ -148,7 +148,7 @@ function StripePaymentAction(props) {
148
148
  onClose?.();
149
149
  };
150
150
  return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
151
- children: [children?.(handlePay, state.paying), state.confirmDialog && /* @__PURE__ */(0, _jsxRuntime.jsx)(_ux.Dialog, {
151
+ children: [children?.(handlePay, state.paying), state.confirmDialog && /* @__PURE__ */(0, _jsxRuntime.jsx)(_dialog.default, {
152
152
  open: state.confirmDialog,
153
153
  title: t("payment.customer.invoice.paymentConfirmTitle"),
154
154
  onClose: handleConfirmCancel,
@@ -154,6 +154,7 @@ const TransactionsTable = _react.default.memo(props => {
154
154
  customBodyRenderLite: (_, index) => {
155
155
  const item = data?.list[index];
156
156
  const isGrant = item.activity_type === "grant";
157
+ const isExpiredGrant = isGrant && item.status === "expired";
157
158
  const amount = isGrant ? item.amount : item.credit_amount;
158
159
  const currency = item.paymentCurrency || item.currency;
159
160
  const unit = !isGrant && item.meter?.unit ? item.meter.unit : currency?.symbol;
@@ -166,13 +167,34 @@ const TransactionsTable = _react.default.memo(props => {
166
167
  })
167
168
  });
168
169
  }
170
+ const amountNode = /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
171
+ sx: {
172
+ color: isGrant ? isExpiredGrant ? "text.disabled" : "success.main" : "error.main"
173
+ },
174
+ children: [isGrant ? "+" : "-", " ", displayAmount]
175
+ });
169
176
  return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
170
177
  onClick: e => handleTransactionClick(e, item),
171
- children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
178
+ children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
179
+ direction: "row",
180
+ spacing: 1,
181
+ alignItems: "center",
182
+ justifyContent: "flex-end",
172
183
  sx: {
173
- color: isGrant ? "success.main" : "error.main"
184
+ width: "100%"
174
185
  },
175
- children: [isGrant ? "+" : "-", " ", displayAmount]
186
+ children: [isExpiredGrant && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Chip, {
187
+ label: t("admin.creditGrants.status.expired"),
188
+ size: "small",
189
+ variant: "outlined",
190
+ sx: {
191
+ mr: 2,
192
+ height: 18,
193
+ fontSize: "12px",
194
+ color: "text.disabled",
195
+ borderColor: "text.disabled"
196
+ }
197
+ }), amountNode]
176
198
  })
177
199
  });
178
200
  }
@@ -184,8 +206,17 @@ const TransactionsTable = _react.default.memo(props => {
184
206
  customBodyRenderLite: (_, index) => {
185
207
  const item = data?.list[index];
186
208
  const isGrant = item.activity_type === "grant";
209
+ const isExpiredGrant = isGrant && item.status === "expired";
187
210
  const grantName = isGrant ? item.name : item.creditGrant.name;
188
211
  const grantId = isGrant ? item.id : item.credit_grant_id;
212
+ const nameNode = /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
213
+ variant: "body2",
214
+ sx: {
215
+ cursor: "pointer",
216
+ color: isExpiredGrant ? "text.disabled" : void 0
217
+ },
218
+ children: grantName || `Grant ${grantId.slice(-6)}`
219
+ });
189
220
  return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
190
221
  direction: "row",
191
222
  spacing: 1,
@@ -196,13 +227,7 @@ const TransactionsTable = _react.default.memo(props => {
196
227
  sx: {
197
228
  alignItems: "center"
198
229
  },
199
- children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
200
- variant: "body2",
201
- sx: {
202
- cursor: "pointer"
203
- },
204
- children: grantName || `Grant ${grantId.slice(-6)}`
205
- })
230
+ children: nameNode
206
231
  });
207
232
  }
208
233
  }
@@ -213,16 +238,19 @@ const TransactionsTable = _react.default.memo(props => {
213
238
  customBodyRenderLite: (_, index) => {
214
239
  const item = data?.list[index];
215
240
  const isGrant = item.activity_type === "grant";
241
+ const isExpiredGrant = isGrant && item.status === "expired";
216
242
  const description = isGrant ? item.name || item.description || "Credit Granted" : item.subscription?.description || item.description || `${item.meter_event_name} usage`;
243
+ const descriptionNode = /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
244
+ variant: "body2",
245
+ sx: {
246
+ fontWeight: 400,
247
+ color: isExpiredGrant ? "text.disabled" : void 0
248
+ },
249
+ children: description
250
+ });
217
251
  return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
218
252
  onClick: e => handleTransactionClick(e, item),
219
- children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
220
- variant: "body2",
221
- sx: {
222
- fontWeight: 400
223
- },
224
- children: description
225
- })
253
+ children: descriptionNode
226
254
  });
227
255
  }
228
256
  }
package/lib/locales/en.js CHANGED
@@ -271,6 +271,10 @@ module.exports = (0, _flat.default)({
271
271
  oneTimeEnoughWithExpiry: "You have a usage overage of {amount}. This purchase adds {totalAmount} (valid for {duration} {unit}). After covering the overage, your new available balance will be {availableAmount}.",
272
272
  recurringEnough: "You have a usage overage of {amount}. This subscription adds {totalAmount} {period}. After covering the overage, your new available balance will be {availableAmount}.",
273
273
  recurringEnoughWithExpiry: "You have a usage overage of {amount}. This subscription adds {totalAmount} {period} (valid for {duration} {unit}). After covering the overage, your new available balance will be {availableAmount}."
274
+ },
275
+ schedule: {
276
+ periodic: "Grant {amount} every {interval}.",
277
+ withRefresh: "Grant {amount} every {interval}; unused credits expire with the next grant."
274
278
  }
275
279
  },
276
280
  expired: {
package/lib/locales/zh.js CHANGED
@@ -308,6 +308,10 @@ module.exports = (0, _flat.default)({
308
308
  oneTimeEnoughWithExpiry: "\u60A8\u6709 {amount} \u7684\u4F7F\u7528\u8D85\u989D\u3002\u672C\u6B21\u8D2D\u4E70\u5C06\u589E\u52A0 {totalAmount}\uFF08\u6709\u6548\u671F {duration} {unit}\uFF09\u3002\u6263\u9664\u8D85\u989D\u540E\uFF0C\u60A8\u7684\u65B0\u53EF\u7528\u4F59\u989D\u5C06\u4E3A {availableAmount}\u3002",
309
309
  recurringEnough: "\u60A8\u6709 {amount} \u7684\u4F7F\u7528\u8D85\u989D\u3002\u672C\u8BA2\u9605{period}\u5C06\u589E\u52A0 {totalAmount}\u3002\u6263\u9664\u8D85\u989D\u540E\uFF0C\u60A8\u7684\u65B0\u53EF\u7528\u4F59\u989D\u5C06\u4E3A {availableAmount}\u3002",
310
310
  recurringEnoughWithExpiry: "\u60A8\u6709 {amount} \u7684\u4F7F\u7528\u8D85\u989D\u3002\u672C\u8BA2\u9605{period}\u5C06\u589E\u52A0 {totalAmount}\uFF08\u6709\u6548\u671F {duration} {unit}\uFF09\u3002\u6263\u9664\u8D85\u989D\u540E\uFF0C\u60A8\u7684\u65B0\u53EF\u7528\u4F59\u989D\u5C06\u4E3A {availableAmount}\u3002"
311
+ },
312
+ schedule: {
313
+ periodic: "\u6BCF{interval}\u53D1\u653E {amount}\u3002",
314
+ withRefresh: "\u6BCF{interval}\u53D1\u653E {amount}\uFF0C\u672A\u4F7F\u7528\u989D\u5EA6\u5C06\u5728\u4E0B\u6B21\u53D1\u653E\u65F6\u8FC7\u671F\u3002"
311
315
  }
312
316
  },
313
317
  emptyItems: {
@@ -76,11 +76,13 @@ function ProductItem({
76
76
  const saving = (0, _util2.formatUpsellSaving)(items, currency);
77
77
  const metered = item.price?.recurring?.usage_type === "metered" ? t("common.metered") : "";
78
78
  const canUpsell = mode === "normal" && items.length === 1;
79
- const isCreditProduct = item.price.product?.type === "credit" && item.price.metadata?.credit_config?.credit_amount;
80
- const creditAmount = isCreditProduct ? Number(item.price.metadata.credit_config.credit_amount) : 0;
81
- const creditCurrency = isCreditProduct ? (0, _util2.findCurrency)(settings.paymentMethods, item.price.metadata?.credit_config?.currency_id ?? "") : null;
79
+ const isCreditProduct = item.price.product?.type === "credit" && item.price.metadata?.credit_config;
80
+ const creditAmount = isCreditProduct && item.price.metadata?.credit_config?.credit_amount ? Number(item.price.metadata.credit_config.credit_amount) : 0;
81
+ const creditCurrency = isCreditProduct && item.price.metadata?.credit_config?.currency_id ? (0, _util2.findCurrency)(settings.paymentMethods, item.price.metadata.credit_config.currency_id) : null;
82
82
  const validDuration = item.price.metadata?.credit_config?.valid_duration_value;
83
83
  const validDurationUnit = item.price.metadata?.credit_config?.valid_duration_unit || "days";
84
+ const scheduleConfig = item.price.metadata?.credit_config?.schedule;
85
+ const hasSchedule = scheduleConfig?.enabled && scheduleConfig?.delivery_mode && scheduleConfig.delivery_mode !== "invoice";
84
86
  const userDid = session?.user?.did;
85
87
  const {
86
88
  data: pendingAmount
@@ -250,6 +252,44 @@ function ProductItem({
250
252
  const type = isRecurring ? "recurringEnough" : "oneTimeEnough";
251
253
  return t(getLocaleKey("pending", type), buildPendingParams(pendingAmountBN, actualAvailable));
252
254
  };
255
+ const formatScheduleInfo = () => {
256
+ if (!hasSchedule || !scheduleConfig) return null;
257
+ const totalCredit = creditAmount * (localQuantity || 0);
258
+ const currencySymbol = creditCurrency?.symbol || "Credits";
259
+ const intervalUnit = scheduleConfig.interval_unit;
260
+ const intervalValue = scheduleConfig.interval_value;
261
+ let amountPerGrant;
262
+ if (scheduleConfig.amount_per_grant) {
263
+ amountPerGrant = Number(scheduleConfig.amount_per_grant) * (localQuantity || 1);
264
+ } else {
265
+ const billingIntervalUnit = item.price.recurring?.interval || "month";
266
+ const billingIntervalCount = item.price.recurring?.interval_count || 1;
267
+ const unitToHours = {
268
+ hour: 1,
269
+ day: 24,
270
+ week: 168,
271
+ month: 720
272
+ // ~30 days
273
+ };
274
+ const billingHours = unitToHours[billingIntervalUnit] * billingIntervalCount;
275
+ const scheduleHours = unitToHours[intervalUnit] * intervalValue;
276
+ const grantsPerPeriod = Math.floor(billingHours / scheduleHours);
277
+ amountPerGrant = grantsPerPeriod > 0 ? totalCredit / grantsPerPeriod : totalCredit;
278
+ }
279
+ const formattedAmount = (0, _util2.formatCreditAmount)((0, _util2.formatNumber)(amountPerGrant), currencySymbol);
280
+ const intervalDisplay = intervalValue === 1 ? t(`common.${intervalUnit}`) : ` ${intervalValue} ${t(`common.${intervalUnit}s`)} `;
281
+ const expireWithNext = scheduleConfig.expire_with_next_grant;
282
+ if (expireWithNext) {
283
+ return t("payment.checkout.credit.schedule.withRefresh", {
284
+ amount: formattedAmount,
285
+ interval: intervalDisplay
286
+ });
287
+ }
288
+ return t("payment.checkout.credit.schedule.periodic", {
289
+ amount: formattedAmount,
290
+ interval: intervalDisplay
291
+ });
292
+ };
253
293
  const primaryText = (0, _react.useMemo)(() => {
254
294
  const price = item.upsell_price || item.price || {};
255
295
  const isRecurring = price?.type === "recurring" && price?.recurring;
@@ -484,7 +524,19 @@ function ProductItem({
484
524
  fontSize: "0.875rem"
485
525
  },
486
526
  icon: false,
487
- children: formatCreditInfo()
527
+ children: hasSchedule ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
528
+ component: "span",
529
+ sx: {
530
+ fontSize: "inherit"
531
+ },
532
+ children: formatScheduleInfo()
533
+ }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
534
+ component: "span",
535
+ sx: {
536
+ fontSize: "inherit"
537
+ },
538
+ children: formatCreditInfo()
539
+ })
488
540
  }), children]
489
541
  }), canUpsell && !item.upsell_price_id && item.price.upsell?.upsells_to && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
490
542
  direction: "row",
@@ -11,7 +11,7 @@ var _context = require("@arcblock/ux/lib/Locale/context");
11
11
  var _material = require("@mui/material");
12
12
  var _react = require("react");
13
13
  var _ufo = require("ufo");
14
- var _ux = require("@arcblock/ux");
14
+ var _Button = _interopRequireDefault(require("@arcblock/ux/lib/Button"));
15
15
  var _payment = require("../contexts/payment");
16
16
  var _progressItem = require("./progress-item");
17
17
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -160,7 +160,7 @@ function PaymentSuccess({
160
160
  borderColor: "grey.300",
161
161
  mx: 2
162
162
  }
163
- }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_ux.Button, {
163
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_Button.default, {
164
164
  variant: "text",
165
165
  color: "primary",
166
166
  size: "small",
@@ -175,7 +175,7 @@ function PaymentSuccess({
175
175
  }, subscription.id))
176
176
  });
177
177
  } else if (subscriptionId) {
178
- next = /* @__PURE__ */(0, _jsxRuntime.jsx)(_ux.Button, {
178
+ next = /* @__PURE__ */(0, _jsxRuntime.jsx)(_Button.default, {
179
179
  variant: "outlined",
180
180
  color: "primary",
181
181
  sx: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/payment-react",
3
- "version": "1.23.11",
3
+ "version": "1.24.1",
4
4
  "description": "Reusable react components for payment kit v2",
5
5
  "keywords": [
6
6
  "react",
@@ -54,18 +54,18 @@
54
54
  }
55
55
  },
56
56
  "dependencies": {
57
- "@arcblock/bridge": "^3.3.10",
58
- "@arcblock/did-connect-react": "^3.3.10",
59
- "@arcblock/react-hooks": "^3.3.10",
60
- "@arcblock/ux": "^3.3.10",
61
- "@arcblock/ws": "^1.28.0",
62
- "@blocklet/theme": "^3.3.10",
63
- "@blocklet/ui-react": "^3.3.10",
57
+ "@arcblock/bridge": "^3.4.7",
58
+ "@arcblock/did-connect-react": "^3.4.7",
59
+ "@arcblock/react-hooks": "^3.4.7",
60
+ "@arcblock/ux": "^3.4.7",
61
+ "@arcblock/ws": "^1.28.5",
62
+ "@blocklet/theme": "^3.4.7",
63
+ "@blocklet/ui-react": "^3.4.7",
64
64
  "@mui/icons-material": "^7.1.2",
65
65
  "@mui/lab": "7.0.0-beta.14",
66
66
  "@mui/material": "^7.1.2",
67
67
  "@mui/system": "^7.1.1",
68
- "@ocap/util": "^1.28.0",
68
+ "@ocap/util": "^1.28.5",
69
69
  "@stripe/react-stripe-js": "^2.9.0",
70
70
  "@stripe/stripe-js": "^2.4.0",
71
71
  "@vitejs/plugin-legacy": "^7.0.0",
@@ -96,7 +96,7 @@
96
96
  "@babel/core": "^7.27.4",
97
97
  "@babel/preset-env": "^7.27.2",
98
98
  "@babel/preset-react": "^7.27.1",
99
- "@blocklet/payment-types": "1.23.11",
99
+ "@blocklet/payment-types": "1.24.1",
100
100
  "@storybook/addon-essentials": "^7.6.20",
101
101
  "@storybook/addon-interactions": "^7.6.20",
102
102
  "@storybook/addon-links": "^7.6.20",
@@ -127,5 +127,5 @@
127
127
  "vite-plugin-babel": "^1.3.1",
128
128
  "vite-plugin-node-polyfills": "^0.23.0"
129
129
  },
130
- "gitHead": "72af80fc6e91e88058665212985002966f55787c"
130
+ "gitHead": "37207c8206743f75ad682d9e020fa04ab7b93e7b"
131
131
  }
@@ -15,7 +15,7 @@ import type {
15
15
  } from '@blocklet/payment-types';
16
16
  import { useRequest } from 'ahooks';
17
17
  import pWaitFor from 'p-wait-for';
18
- import { Dialog } from '@arcblock/ux';
18
+ import Dialog from '@arcblock/ux/lib/Dialog/dialog';
19
19
  import { CheckCircle as CheckCircleIcon } from '@mui/icons-material';
20
20
  import debounce from 'lodash/debounce';
21
21
  import { usePaymentContext } from '../contexts/payment';
@@ -5,7 +5,7 @@ import Toast from '@arcblock/ux/lib/Toast';
5
5
  import { joinURL } from 'ufo';
6
6
  import type { Subscription, TSubscriptionExpanded } from '@blocklet/payment-types';
7
7
  import { useRequest } from 'ahooks';
8
- import { Dialog } from '@arcblock/ux';
8
+ import Dialog from '@arcblock/ux/lib/Dialog/dialog';
9
9
  import { usePaymentContext } from '../contexts/payment';
10
10
  import { formatError, formatToDate, getPrefix, isCrossOrigin } from '../libs/util';
11
11
  import api from '../libs/api';
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable react/require-default-props */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
3
  import Toast from '@arcblock/ux/lib/Toast';
4
- import { Dialog } from '@arcblock/ux';
4
+ import Dialog from '@arcblock/ux/lib/Dialog/dialog';
5
5
  import type { Customer, TPaymentMethod, TInvoiceExpanded } from '@blocklet/payment-types';
6
6
  import { Button, Typography } from '@mui/material';
7
7
  import { useSetState } from 'ahooks';
@@ -6,7 +6,7 @@
6
6
  /* eslint-disable react/no-unstable-nested-components */
7
7
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
8
8
  import type { Paginated, TCreditTransactionExpanded } from '@blocklet/payment-types';
9
- import { Box, Typography, Grid, Stack, Link, Button } from '@mui/material';
9
+ import { Box, Typography, Grid, Stack, Link, Button, Chip } from '@mui/material';
10
10
  import { useRequest } from 'ahooks';
11
11
  // eslint-disable-next-line import/no-extraneous-dependencies
12
12
  import { useNavigate } from 'react-router-dom';
@@ -181,6 +181,7 @@ const TransactionsTable = React.memo((props: Props) => {
181
181
  customBodyRenderLite: (_: string, index: number) => {
182
182
  const item = data?.list[index] as any;
183
183
  const isGrant = item.activity_type === 'grant';
184
+ const isExpiredGrant = isGrant && item.status === 'expired';
184
185
  const amount = isGrant ? item.amount : item.credit_amount;
185
186
  const currency = item.paymentCurrency || item.currency;
186
187
  const unit = !isGrant && item.meter?.unit ? item.meter.unit : currency?.symbol;
@@ -194,14 +195,34 @@ const TransactionsTable = React.memo((props: Props) => {
194
195
  );
195
196
  }
196
197
 
198
+ const amountNode = (
199
+ <Typography
200
+ sx={{
201
+ color: isGrant ? (isExpiredGrant ? 'text.disabled' : 'success.main') : 'error.main',
202
+ }}>
203
+ {isGrant ? '+' : '-'} {displayAmount}
204
+ </Typography>
205
+ );
206
+
197
207
  return (
198
208
  <Box onClick={(e) => handleTransactionClick(e, item)}>
199
- <Typography
200
- sx={{
201
- color: isGrant ? 'success.main' : 'error.main',
202
- }}>
203
- {isGrant ? '+' : '-'} {displayAmount}
204
- </Typography>
209
+ <Stack direction="row" spacing={1} alignItems="center" justifyContent="flex-end" sx={{ width: '100%' }}>
210
+ {isExpiredGrant && (
211
+ <Chip
212
+ label={t('admin.creditGrants.status.expired')}
213
+ size="small"
214
+ variant="outlined"
215
+ sx={{
216
+ mr: 2,
217
+ height: 18,
218
+ fontSize: '12px',
219
+ color: 'text.disabled',
220
+ borderColor: 'text.disabled',
221
+ }}
222
+ />
223
+ )}
224
+ {amountNode}
225
+ </Stack>
205
226
  </Box>
206
227
  );
207
228
  },
@@ -214,9 +235,15 @@ const TransactionsTable = React.memo((props: Props) => {
214
235
  customBodyRenderLite: (_: string, index: number) => {
215
236
  const item = data?.list[index] as any;
216
237
  const isGrant = item.activity_type === 'grant';
238
+ const isExpiredGrant = isGrant && item.status === 'expired';
217
239
 
218
240
  const grantName = isGrant ? item.name : item.creditGrant.name;
219
241
  const grantId = isGrant ? item.id : item.credit_grant_id;
242
+ const nameNode = (
243
+ <Typography variant="body2" sx={{ cursor: 'pointer', color: isExpiredGrant ? 'text.disabled' : undefined }}>
244
+ {grantName || `Grant ${grantId.slice(-6)}`}
245
+ </Typography>
246
+ );
220
247
  return (
221
248
  <Stack
222
249
  direction="row"
@@ -228,9 +255,7 @@ const TransactionsTable = React.memo((props: Props) => {
228
255
  sx={{
229
256
  alignItems: 'center',
230
257
  }}>
231
- <Typography variant="body2" sx={{ cursor: 'pointer' }}>
232
- {grantName || `Grant ${grantId.slice(-6)}`}
233
- </Typography>
258
+ {nameNode}
234
259
  </Stack>
235
260
  );
236
261
  },
@@ -243,17 +268,18 @@ const TransactionsTable = React.memo((props: Props) => {
243
268
  customBodyRenderLite: (_: string, index: number) => {
244
269
  const item = data?.list[index] as any;
245
270
  const isGrant = item.activity_type === 'grant';
271
+ const isExpiredGrant = isGrant && item.status === 'expired';
246
272
  const description = isGrant
247
273
  ? item.name || item.description || 'Credit Granted'
248
274
  : item.subscription?.description || item.description || `${item.meter_event_name} usage`;
249
275
 
250
- return (
251
- <Box onClick={(e) => handleTransactionClick(e, item)}>
252
- <Typography variant="body2" sx={{ fontWeight: 400 }}>
253
- {description}
254
- </Typography>
255
- </Box>
276
+ const descriptionNode = (
277
+ <Typography variant="body2" sx={{ fontWeight: 400, color: isExpiredGrant ? 'text.disabled' : undefined }}>
278
+ {description}
279
+ </Typography>
256
280
  );
281
+
282
+ return <Box onClick={(e) => handleTransactionClick(e, item)}>{descriptionNode}</Box>;
257
283
  },
258
284
  },
259
285
  },
@@ -276,6 +276,10 @@ export default flat({
276
276
  recurringEnoughWithExpiry:
277
277
  'You have a usage overage of {amount}. This subscription adds {totalAmount} {period} (valid for {duration} {unit}). After covering the overage, your new available balance will be {availableAmount}.',
278
278
  },
279
+ schedule: {
280
+ periodic: 'Grant {amount} every {interval}.',
281
+ withRefresh: 'Grant {amount} every {interval}; unused credits expire with the next grant.',
282
+ },
279
283
  },
280
284
  expired: {
281
285
  title: 'Expired Link',
@@ -309,6 +309,10 @@ export default flat({
309
309
  recurringEnoughWithExpiry:
310
310
  '您有 {amount} 的使用超额。本订阅{period}将增加 {totalAmount}(有效期 {duration} {unit})。扣除超额后,您的新可用余额将为 {availableAmount}。',
311
311
  },
312
+ schedule: {
313
+ periodic: '每{interval}发放 {amount}。',
314
+ withRefresh: '每{interval}发放 {amount},未使用额度将在下次发放时过期。',
315
+ },
312
316
  },
313
317
  emptyItems: {
314
318
  title: '没有任何购买项目',
@@ -18,6 +18,7 @@ import {
18
18
  formatUpsellSaving,
19
19
  formatAmount,
20
20
  formatCreditForCheckout,
21
+ formatCreditAmount,
21
22
  formatBNStr,
22
23
  } from '../libs/util';
23
24
  import ProductCard from './product-card';
@@ -96,13 +97,21 @@ export default function ProductItem({
96
97
  const metered = item.price?.recurring?.usage_type === 'metered' ? t('common.metered') : '';
97
98
  const canUpsell = mode === 'normal' && items.length === 1;
98
99
 
99
- const isCreditProduct = item.price.product?.type === 'credit' && item.price.metadata?.credit_config?.credit_amount;
100
- const creditAmount = isCreditProduct ? Number(item.price.metadata.credit_config.credit_amount) : 0;
101
- const creditCurrency = isCreditProduct
102
- ? findCurrency(settings.paymentMethods, item.price.metadata?.credit_config?.currency_id ?? '')
103
- : null;
100
+ // Check if this is a credit product - be more lenient in detection
101
+ const isCreditProduct = item.price.product?.type === 'credit' && item.price.metadata?.credit_config;
102
+ const creditAmount =
103
+ isCreditProduct && item.price.metadata?.credit_config?.credit_amount
104
+ ? Number(item.price.metadata.credit_config.credit_amount)
105
+ : 0;
106
+ const creditCurrency =
107
+ isCreditProduct && item.price.metadata?.credit_config?.currency_id
108
+ ? findCurrency(settings.paymentMethods, item.price.metadata.credit_config.currency_id)
109
+ : null;
104
110
  const validDuration = item.price.metadata?.credit_config?.valid_duration_value;
105
111
  const validDurationUnit = item.price.metadata?.credit_config?.valid_duration_unit || 'days';
112
+ const scheduleConfig = item.price.metadata?.credit_config?.schedule;
113
+ const hasSchedule =
114
+ scheduleConfig?.enabled && scheduleConfig?.delivery_mode && scheduleConfig.delivery_mode !== 'invoice';
106
115
 
107
116
  const userDid = session?.user?.did;
108
117
 
@@ -318,6 +327,58 @@ export default function ProductItem({
318
327
  return t(getLocaleKey('pending', type), buildPendingParams(pendingAmountBN, actualAvailable));
319
328
  };
320
329
 
330
+ // Format credit schedule info for display
331
+ const formatScheduleInfo = () => {
332
+ if (!hasSchedule || !scheduleConfig) return null;
333
+
334
+ const totalCredit = creditAmount * (localQuantity || 0);
335
+ const currencySymbol = creditCurrency?.symbol || 'Credits';
336
+ const intervalUnit = scheduleConfig.interval_unit;
337
+ const intervalValue = scheduleConfig.interval_value;
338
+
339
+ // Calculate amount per grant
340
+ let amountPerGrant: number;
341
+ if (scheduleConfig.amount_per_grant) {
342
+ amountPerGrant = Number(scheduleConfig.amount_per_grant) * (localQuantity || 1);
343
+ } else {
344
+ // Divide total by period intervals
345
+ const billingIntervalUnit = item.price.recurring?.interval || 'month';
346
+ const billingIntervalCount = item.price.recurring?.interval_count || 1;
347
+
348
+ // Calculate how many schedule intervals fit in billing period
349
+ const unitToHours: Record<string, number> = {
350
+ hour: 1,
351
+ day: 24,
352
+ week: 168,
353
+ month: 720, // ~30 days
354
+ };
355
+ const billingHours = unitToHours[billingIntervalUnit] * billingIntervalCount;
356
+ const scheduleHours = unitToHours[intervalUnit] * intervalValue;
357
+ const grantsPerPeriod = Math.floor(billingHours / scheduleHours);
358
+ amountPerGrant = grantsPerPeriod > 0 ? totalCredit / grantsPerPeriod : totalCredit;
359
+ }
360
+ const formattedAmount = formatCreditAmount(formatNumber(amountPerGrant), currencySymbol);
361
+
362
+ const intervalDisplay =
363
+ intervalValue === 1
364
+ ? t(`common.${intervalUnit}`)
365
+ : ` ${intervalValue} ${t(`common.${intervalUnit}s` as 'common.hours' | 'common.days' | 'common.weeks' | 'common.months')} `;
366
+
367
+ const expireWithNext = scheduleConfig.expire_with_next_grant;
368
+
369
+ if (expireWithNext) {
370
+ return t('payment.checkout.credit.schedule.withRefresh', {
371
+ amount: formattedAmount,
372
+ interval: intervalDisplay,
373
+ });
374
+ }
375
+
376
+ return t('payment.checkout.credit.schedule.periodic', {
377
+ amount: formattedAmount,
378
+ interval: intervalDisplay,
379
+ });
380
+ };
381
+
321
382
  const primaryText = useMemo(() => {
322
383
  const price = item.upsell_price || item.price || {};
323
384
  const isRecurring = price?.type === 'recurring' && price?.recurring;
@@ -534,7 +595,15 @@ export default function ProductItem({
534
595
  {/* Credit 信息展示 */}
535
596
  {isCreditProduct && (
536
597
  <Alert severity="info" sx={{ mt: 1, fontSize: '0.875rem' }} icon={false}>
537
- {formatCreditInfo()}
598
+ {hasSchedule ? (
599
+ <Typography component="span" sx={{ fontSize: 'inherit' }}>
600
+ {formatScheduleInfo()}
601
+ </Typography>
602
+ ) : (
603
+ <Typography component="span" sx={{ fontSize: 'inherit' }}>
604
+ {formatCreditInfo()}
605
+ </Typography>
606
+ )}
538
607
  </Alert>
539
608
  )}
540
609
 
@@ -5,7 +5,7 @@ import { Box, Grow, Link, Paper, Stack, styled, Typography } from '@mui/material
5
5
  import { useEffect, useRef, useState } from 'react';
6
6
  import { joinURL } from 'ufo';
7
7
 
8
- import { Button } from '@arcblock/ux';
8
+ import Button from '@arcblock/ux/lib/Button';
9
9
  import { usePaymentContext } from '../contexts/payment';
10
10
  import { VendorPlaceholder, VendorProgressItem } from './progress-item';
11
11