payment-kit 1.27.2 → 1.28.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.
Files changed (184) hide show
  1. package/__blocklet__.js +37 -0
  2. package/api/ocap-1.30-subpath-shims.d.ts +35 -0
  3. package/api/src/crons/index.ts +10 -0
  4. package/api/src/crons/metering-subscription-detection.ts +12 -14
  5. package/api/src/crons/overdue-detection.ts +51 -74
  6. package/api/src/integrations/arcblock/nft.ts +6 -2
  7. package/api/src/integrations/arcblock/stake.ts +3 -2
  8. package/api/src/integrations/arcblock/token.ts +4 -4
  9. package/api/src/integrations/blocklet/notification.ts +1 -1
  10. package/api/src/integrations/ethereum/tx.ts +29 -0
  11. package/api/src/integrations/stripe/handlers/invoice.ts +70 -53
  12. package/api/src/integrations/stripe/handlers/payment-intent.ts +8 -1
  13. package/api/src/integrations/stripe/resource.ts +8 -0
  14. package/api/src/libs/audit.ts +32 -16
  15. package/api/src/libs/auth.ts +49 -2
  16. package/api/src/libs/chain-error.ts +31 -0
  17. package/api/src/libs/error.ts +15 -0
  18. package/api/src/libs/event.ts +42 -1
  19. package/api/src/libs/invoice.ts +69 -34
  20. package/api/src/libs/notification/template/customer-auto-recharge-daily-limit-exceeded.ts +1 -3
  21. package/api/src/libs/notification/template/customer-auto-recharge-failed.ts +1 -3
  22. package/api/src/libs/notification/template/customer-credit-grant-granted.ts +1 -3
  23. package/api/src/libs/notification/template/customer-credit-insufficient.ts +1 -3
  24. package/api/src/libs/notification/template/customer-credit-low-balance.ts +1 -3
  25. package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -3
  26. package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -3
  27. package/api/src/libs/notification/template/one-time-payment-refund-succeeded.ts +1 -3
  28. package/api/src/libs/notification/template/one-time-payment-succeeded.ts +1 -3
  29. package/api/src/libs/notification/template/subscription-renew-failed.ts +1 -3
  30. package/api/src/libs/notification/template/subscription-slippage-exceeded.ts +1 -3
  31. package/api/src/libs/notification/template/subscription-slippage-warning.ts +1 -3
  32. package/api/src/libs/notification/template/subscription-succeeded.ts +1 -1
  33. package/api/src/libs/pagination.ts +14 -9
  34. package/api/src/libs/payment.ts +25 -10
  35. package/api/src/libs/session.ts +1 -1
  36. package/api/src/libs/timing.ts +35 -0
  37. package/api/src/libs/util.ts +16 -15
  38. package/api/src/libs/wallet-migration.ts +72 -53
  39. package/api/src/queues/auto-recharge.ts +1 -1
  40. package/api/src/queues/credit-consume.ts +94 -12
  41. package/api/src/queues/credit-grant.ts +4 -0
  42. package/api/src/queues/event.ts +14 -2
  43. package/api/src/queues/invoice.ts +1 -0
  44. package/api/src/queues/payment.ts +83 -15
  45. package/api/src/queues/refund.ts +84 -71
  46. package/api/src/queues/subscription.ts +1 -0
  47. package/api/src/routes/checkout-sessions.ts +82 -43
  48. package/api/src/routes/connect/change-payment.ts +2 -0
  49. package/api/src/routes/connect/change-plan.ts +2 -0
  50. package/api/src/routes/connect/pay.ts +12 -3
  51. package/api/src/routes/connect/setup.ts +3 -1
  52. package/api/src/routes/connect/shared.ts +52 -39
  53. package/api/src/routes/connect/subscribe.ts +4 -1
  54. package/api/src/routes/credit-grants.ts +25 -17
  55. package/api/src/routes/donations.ts +2 -2
  56. package/api/src/routes/meter-events.ts +16 -6
  57. package/api/src/routes/payment-links.ts +1 -1
  58. package/api/src/routes/payment-methods.ts +1 -1
  59. package/api/src/routes/settings.ts +1 -1
  60. package/api/src/routes/tax-rates.ts +1 -1
  61. package/api/src/store/models/customer.ts +23 -1
  62. package/api/src/store/models/payment-method.ts +4 -0
  63. package/api/src/store/models/price.ts +23 -14
  64. package/api/tests/libs/wallet-migration.spec.ts +4 -4
  65. package/api/tests/queues/credit-consume-batch.spec.ts +5 -2
  66. package/api/tests/queues/credit-consume.spec.ts +8 -4
  67. package/api/tests/routes/credit-grants.spec.ts +1 -0
  68. package/blocklet.yml +1 -1
  69. package/cloudflare/MIGRATION-CHALLENGES.md +676 -0
  70. package/cloudflare/MIGRATION-RUNBOOK.md +777 -0
  71. package/cloudflare/README.md +499 -0
  72. package/cloudflare/STAGING-MIGRATION-GUIDE.md +602 -0
  73. package/cloudflare/build.ts +151 -0
  74. package/cloudflare/did-connect-auth.ts +527 -0
  75. package/cloudflare/docs/2026-04-22-sdk-1.30.9-upgrade-retro.md +324 -0
  76. package/cloudflare/docs/2026-04-24-queue-ops-followup.md +218 -0
  77. package/cloudflare/docs/cf-queues-ops-alert-analysis.md +663 -0
  78. package/cloudflare/docs/cf-workers-local-dev-and-fixes.md +284 -0
  79. package/cloudflare/docs/cleanup-tasks-2026-05.md +62 -0
  80. package/cloudflare/docs/payment-kit-platform-analysis-2026-04-20.md +354 -0
  81. package/cloudflare/frontend-shims/buffer-polyfill.ts +9 -0
  82. package/cloudflare/frontend-shims/js-sdk.ts +43 -0
  83. package/cloudflare/frontend-shims/mime-types.ts +46 -0
  84. package/cloudflare/frontend-shims/session.ts +24 -0
  85. package/cloudflare/frontend-shims/vite-plugin-noop.ts +6 -0
  86. package/cloudflare/index.html +40 -0
  87. package/cloudflare/migrate-to-d1.js +252 -0
  88. package/cloudflare/migrations/0001_initial_schema.sql +82 -0
  89. package/cloudflare/migrations/0002_indexes.sql +75 -0
  90. package/cloudflare/migrations/0003_locks_and_constraints.sql +18 -0
  91. package/cloudflare/run-build.js +390 -0
  92. package/cloudflare/scripts/test-decrypt.js +102 -0
  93. package/cloudflare/shims/arcblock-ws.ts +20 -0
  94. package/cloudflare/shims/axios-http-adapter.ts +4 -0
  95. package/cloudflare/shims/axios-lite.ts +117 -0
  96. package/cloudflare/shims/blocklet-sdk/auth-service.ts +33 -0
  97. package/cloudflare/shims/blocklet-sdk/cdn.ts +3 -0
  98. package/cloudflare/shims/blocklet-sdk/component-api.ts +35 -0
  99. package/cloudflare/shims/blocklet-sdk/component.ts +18 -0
  100. package/cloudflare/shims/blocklet-sdk/config.ts +8 -0
  101. package/cloudflare/shims/blocklet-sdk/did.ts +14 -0
  102. package/cloudflare/shims/blocklet-sdk/env.ts +12 -0
  103. package/cloudflare/shims/blocklet-sdk/eventbus.ts +3 -0
  104. package/cloudflare/shims/blocklet-sdk/fallback.ts +3 -0
  105. package/cloudflare/shims/blocklet-sdk/index.ts +11 -0
  106. package/cloudflare/shims/blocklet-sdk/logger.ts +11 -0
  107. package/cloudflare/shims/blocklet-sdk/middlewares.ts +15 -0
  108. package/cloudflare/shims/blocklet-sdk/notification.ts +11 -0
  109. package/cloudflare/shims/blocklet-sdk/security.ts +53 -0
  110. package/cloudflare/shims/blocklet-sdk/session.ts +8 -0
  111. package/cloudflare/shims/blocklet-sdk/verify-sign.ts +38 -0
  112. package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +3 -0
  113. package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +6 -0
  114. package/cloudflare/shims/blocklet-sdk/wallet.ts +103 -0
  115. package/cloudflare/shims/cookie-parser.ts +3 -0
  116. package/cloudflare/shims/cors.ts +21 -0
  117. package/cloudflare/shims/cron.ts +189 -0
  118. package/cloudflare/shims/crypto-js-warn.ts +7 -0
  119. package/cloudflare/shims/did-space-js.ts +17 -0
  120. package/cloudflare/shims/did-space.ts +11 -0
  121. package/cloudflare/shims/error.ts +18 -0
  122. package/cloudflare/shims/express-compat/index.ts +80 -0
  123. package/cloudflare/shims/express-compat/types.ts +41 -0
  124. package/cloudflare/shims/fastq.ts +105 -0
  125. package/cloudflare/shims/lock.ts +115 -0
  126. package/cloudflare/shims/mime-types.ts +56 -0
  127. package/cloudflare/shims/nedb-storage.ts +9 -0
  128. package/cloudflare/shims/node-child-process.ts +9 -0
  129. package/cloudflare/shims/node-fs.ts +20 -0
  130. package/cloudflare/shims/node-http.ts +13 -0
  131. package/cloudflare/shims/node-https.ts +4 -0
  132. package/cloudflare/shims/node-misc.ts +15 -0
  133. package/cloudflare/shims/node-net.ts +8 -0
  134. package/cloudflare/shims/node-os.ts +14 -0
  135. package/cloudflare/shims/node-tty.ts +8 -0
  136. package/cloudflare/shims/node-zlib.ts +17 -0
  137. package/cloudflare/shims/noop.ts +26 -0
  138. package/cloudflare/shims/payment-vendor.ts +14 -0
  139. package/cloudflare/shims/querystring.ts +12 -0
  140. package/cloudflare/shims/queue.ts +585 -0
  141. package/cloudflare/shims/rolldown-runtime.ts +43 -0
  142. package/cloudflare/shims/sequelize-d1/datatypes.ts +24 -0
  143. package/cloudflare/shims/sequelize-d1/helpers.ts +46 -0
  144. package/cloudflare/shims/sequelize-d1/index.ts +34 -0
  145. package/cloudflare/shims/sequelize-d1/model.ts +1157 -0
  146. package/cloudflare/shims/sequelize-d1/operators.ts +293 -0
  147. package/cloudflare/shims/sequelize-d1/retry.ts +85 -0
  148. package/cloudflare/shims/sequelize-d1/sequelize-class.ts +119 -0
  149. package/cloudflare/shims/sequelize-d1/timing.ts +81 -0
  150. package/cloudflare/shims/sequelize-d1/types.ts +35 -0
  151. package/cloudflare/shims/stripe-cf.ts +29 -0
  152. package/cloudflare/shims/ws-lite.ts +103 -0
  153. package/cloudflare/shims/xss.ts +3 -0
  154. package/cloudflare/tests/shims/cron.spec.ts +210 -0
  155. package/cloudflare/tests/shims/queue-scheduled.spec.ts +186 -0
  156. package/cloudflare/vite.config.ts +162 -0
  157. package/cloudflare/worker.ts +1553 -0
  158. package/cloudflare/wrangler.json +63 -0
  159. package/cloudflare/wrangler.jsonc +69 -0
  160. package/cloudflare/wrangler.staging.json +66 -0
  161. package/cloudflare/wrangler.toml +28 -0
  162. package/jest.config.js +4 -12
  163. package/package.json +26 -22
  164. package/src/app.tsx +62 -4
  165. package/src/components/customer/link.tsx +9 -13
  166. package/src/components/customer/notification-preference.tsx +3 -2
  167. package/src/components/filter-toolbar.tsx +4 -0
  168. package/src/components/invoice/list.tsx +9 -1
  169. package/src/components/invoice-pdf/utils.ts +2 -1
  170. package/src/components/layout/admin.tsx +39 -5
  171. package/src/components/layout/user-cf.tsx +77 -0
  172. package/src/components/payment-intent/actions.tsx +23 -3
  173. package/src/components/safe-did-address.tsx +75 -0
  174. package/src/libs/patch-user-card.ts +25 -0
  175. package/src/libs/util.ts +5 -7
  176. package/src/pages/admin/billing/meter-events/index.tsx +4 -0
  177. package/src/pages/admin/customers/customers/detail.tsx +2 -2
  178. package/src/pages/admin/customers/customers/index.tsx +2 -2
  179. package/src/pages/admin/overview.tsx +3 -1
  180. package/src/pages/customer/subscription/detail.tsx +4 -4
  181. package/tsconfig.api.json +1 -6
  182. package/tsconfig.json +3 -4
  183. package/tsconfig.types.json +2 -1
  184. package/vite.config.ts +6 -1
@@ -0,0 +1,77 @@
1
+ /* eslint-disable react-hooks/exhaustive-deps */
2
+ import { PaymentProvider } from '@blocklet/payment-react';
3
+ import { useEffect } from 'react';
4
+ import { Header } from '@blocklet/ui-react';
5
+ import { Box, CircularProgress } from '@mui/material';
6
+ import { useSessionContext } from '../../contexts/session';
7
+
8
+ function LayoutLoading() {
9
+ return (
10
+ <Box
11
+ sx={{
12
+ minHeight: '100vh',
13
+ display: 'flex',
14
+ alignItems: 'center',
15
+ justifyContent: 'center',
16
+ }}>
17
+ <CircularProgress />
18
+ </Box>
19
+ );
20
+ }
21
+
22
+ // window.blocklet is bootstrapped synchronously in public/index.html but the
23
+ // full payload is overlaid from /__blocklet__.js. Treat the layout as "not
24
+ // ready" until the overlay has populated the fields the downstream components
25
+ // (Header, PaymentProvider) rely on, so we never render children against a
26
+ // half-initialized blocklet config.
27
+ function isBlockletReady() {
28
+ const b = (window as any).blocklet;
29
+ return !!(b && b.appPid && b.prefix);
30
+ }
31
+
32
+ /**
33
+ * CF Workers layout for customer pages.
34
+ * Uses Dashboard component for consistent header + SessionUser dropdown.
35
+ * Adds "My Billing" to the user dropdown via sessionManagerProps.menu.
36
+ */
37
+ export default function UserLayoutCF(props: any) {
38
+ const { session, connectApi } = useSessionContext();
39
+
40
+ useEffect(() => {
41
+ if (session.initialized && !session.user) {
42
+ session.login(() => {});
43
+ }
44
+ }, [session.initialized]);
45
+
46
+ // Show a loading state (not a blank page, not a redirect) while either
47
+ // window.blocklet or the session is still being resolved. Any child that
48
+ // fires API calls on mount (useRequest / useEffect) must not run until
49
+ // both are in place — otherwise the request goes out without the right
50
+ // auth cookies/headers and may cascade into a redirect.
51
+ if (!isBlockletReady() || !session.initialized) {
52
+ return <LayoutLoading />;
53
+ }
54
+
55
+ // Session initialized but not logged in: the effect above will trigger the
56
+ // DID Connect login modal. Keep showing the loading state instead of a
57
+ // blank screen so the user doesn't perceive it as "bounced to home".
58
+ if (!session.user) {
59
+ return <LayoutLoading />;
60
+ }
61
+
62
+ return (
63
+ <PaymentProvider session={session} connect={connectApi}>
64
+ <Header
65
+ meta={undefined}
66
+ addons={undefined}
67
+ sessionManagerProps={undefined}
68
+ homeLink={undefined}
69
+ theme={undefined}
70
+ hideNavMenu={undefined}
71
+ maxWidth={false}
72
+ sx={{ borderBottom: '1px solid', borderColor: 'divider' }}
73
+ />
74
+ <Box sx={{ flex: 1, maxWidth: 1200, mx: 'auto', px: 3, py: 3, width: '100%' }}>{props.children}</Box>
75
+ </PaymentProvider>
76
+ );
77
+ }
@@ -1,7 +1,8 @@
1
1
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
2
  import Toast from '@arcblock/ux/lib/Toast';
3
- import { ConfirmDialog, api, formatAmountPrecisionLimit, formatBNStr, formatError } from '@blocklet/payment-react';
3
+ import { ConfirmDialog, api, formatAmountPrecisionLimit, formatError } from '@blocklet/payment-react';
4
4
  import type { TPaymentIntentExpanded } from '@blocklet/payment-types';
5
+ import { fromUnitToToken } from '@ocap/util';
5
6
  import { useRequest, useSetState } from 'ahooks';
6
7
  import { useNavigate } from 'react-router-dom';
7
8
  import type { LiteralUnion } from 'type-fest';
@@ -21,6 +22,25 @@ import { useState } from 'react';
21
22
  import Actions from '../actions';
22
23
  import ClickBoundary from '../click-boundary';
23
24
 
25
+ /**
26
+ * Format the refundable amount the server returned (raw BN string) into a human-readable
27
+ * token string by **flooring** to `precision` decimal places — never rounding up.
28
+ *
29
+ * `formatBNStr` rounds (via toLocaleString / toFixed), which can produce a value whose
30
+ * `fromTokenToUnit` round-trip exceeds the original raw amount by sub-cent dust and gets
31
+ * rejected by the server with "refund amount exceeds the available amount". Flooring keeps
32
+ * the round-trip ≤ original. The trailing-zero trim mirrors `formatBNStr` so the input
33
+ * field looks identical for amounts that don't get truncated.
34
+ */
35
+ function formatRefundableAmount(raw: string | undefined, decimals: number, precision: number = 6): string {
36
+ if (!raw) return '0';
37
+ const tokenAmount = fromUnitToToken(raw, decimals);
38
+ const [whole = '0', frac = ''] = tokenAmount.split('.');
39
+ if (precision <= 0) return whole;
40
+ const truncated = frac.substring(0, precision).replace(/0+$/, '');
41
+ return truncated ? `${whole}.${truncated}` : whole;
42
+ }
43
+
24
44
  type Props = {
25
45
  data: TPaymentIntentExpanded;
26
46
  variant?: LiteralUnion<'compact' | 'normal', string>;
@@ -185,7 +205,7 @@ export function PaymentIntentActionsInner({ data, variant = 'compact', onChange
185
205
  },
186
206
  {
187
207
  onSuccess: (res: any) => {
188
- const amount = formatBNStr(res?.amount, data.paymentCurrency.decimal);
208
+ const amount = formatRefundableAmount(res?.amount, data.paymentCurrency.decimal);
189
209
  setRefundMaxAmount(amount);
190
210
  },
191
211
  manual: true,
@@ -218,7 +238,7 @@ export function PaymentIntentActionsInner({ data, variant = 'compact', onChange
218
238
  handler: () => {
219
239
  runRefundAmountAsync().then((res) => {
220
240
  reset();
221
- const curAmount = formatBNStr(res?.amount, data.paymentCurrency.decimal);
241
+ const curAmount = formatRefundableAmount(res?.amount, data.paymentCurrency.decimal);
222
242
  if (Number(curAmount) <= 0) {
223
243
  Toast.info(t('admin.paymentIntent.refundForm.empty'));
224
244
  return;
@@ -0,0 +1,75 @@
1
+ import DidAddress from '@arcblock/ux/lib/DID';
2
+ import { Typography } from '@mui/material';
3
+ import { Component, type ReactNode } from 'react';
4
+
5
+ /**
6
+ * Matches DIDs that @arcblock/did-motif can parse:
7
+ * z-prefixed base58 DID (z1..., z2..., zNK...) — standard ArcBlock format
8
+ * did:method:identifier — W3C DID format
9
+ */
10
+ function isValidDid(did: unknown): did is string {
11
+ return typeof did === 'string' && /^(z[0-9a-zA-Z]{20,}|did:[a-z]+:[A-Za-z0-9]+)/.test(did);
12
+ }
13
+
14
+ interface ErrorBoundaryProps {
15
+ fallback: ReactNode;
16
+ children: ReactNode;
17
+ }
18
+
19
+ /**
20
+ * Inline error boundary — catches DIDParsingError (or any render error) from DidAddress
21
+ * and shows a fallback instead of crashing the whole page.
22
+ */
23
+ class DidErrorBoundary extends Component<ErrorBoundaryProps, { hasError: boolean }> {
24
+ // eslint-disable-next-line react/state-in-constructor
25
+ override state = { hasError: false };
26
+
27
+ static getDerivedStateFromError() {
28
+ return { hasError: true };
29
+ }
30
+
31
+ override componentDidCatch() {
32
+ // Swallow — DIDParsingError is non-fatal, we just show a fallback
33
+ }
34
+
35
+ override render() {
36
+ if (this.state.hasError) return this.props.fallback;
37
+ return this.props.children;
38
+ }
39
+ }
40
+
41
+ export interface SafeDidAddressProps {
42
+ did?: string | null;
43
+ [key: string]: any;
44
+ }
45
+
46
+ /**
47
+ * SafeDidAddress — drop-in replacement for `@arcblock/ux/lib/DID` that:
48
+ * 1. Shows an em-dash placeholder for empty/null DIDs
49
+ * 2. Pre-validates the DID format before rendering
50
+ * 3. Catches any render errors from did-motif via an error boundary
51
+ *
52
+ * Use this anywhere the backing data may have invalid/missing DIDs (e.g. migrated
53
+ * customer records with empty did fields).
54
+ */
55
+ // eslint-disable-next-line react/require-default-props
56
+ export default function SafeDidAddress({ did, ...rest }: SafeDidAddressProps) {
57
+ if (!did || !isValidDid(did)) {
58
+ return (
59
+ <Typography variant="body2" color="text.secondary" component="span">
60
+ {did || '—'}
61
+ </Typography>
62
+ );
63
+ }
64
+
65
+ return (
66
+ <DidErrorBoundary
67
+ fallback={
68
+ <Typography variant="body2" color="text.secondary" component="span">
69
+ {did}
70
+ </Typography>
71
+ }>
72
+ <DidAddress did={did} {...rest} />
73
+ </DidErrorBoundary>
74
+ );
75
+ }
@@ -0,0 +1,25 @@
1
+ // UserCard (@arcblock/ux) calls sdk.user.getUserPublicInfo per mount without
2
+ // in-flight dedup. Pages that render many rows with the same DID fire N
3
+ // concurrent requests before the first response warms sessionStorage.
4
+ // Patch the bundled SDK singleton so concurrent calls with the same args
5
+ // share one Promise.
6
+ // Deep import into ux's nested js-sdk copy (intentional; that bundle exposes
7
+ // the SDK singleton UserCard actually calls into — patching any other copy
8
+ // of @blocklet/js-sdk doesn't affect UserCard).
9
+ // eslint-disable-next-line import/extensions
10
+ import { getBlockletSDK } from '@arcblock/ux/lib/node_modules/@blocklet/js-sdk/dist/index.js';
11
+
12
+ type PublicInfoArgs = { did?: string; name?: string };
13
+
14
+ const sdk = getBlockletSDK() as { user: { getUserPublicInfo: (args: PublicInfoArgs) => Promise<unknown> } };
15
+ const original = sdk.user.getUserPublicInfo.bind(sdk.user);
16
+ const inflight = new Map<string, Promise<unknown>>();
17
+
18
+ sdk.user.getUserPublicInfo = (args: PublicInfoArgs) => {
19
+ const key = `${args?.did || ''}|${args?.name || ''}`;
20
+ const pending = inflight.get(key);
21
+ if (pending) return pending;
22
+ const promise = original(args).finally(() => inflight.delete(key));
23
+ inflight.set(key, promise);
24
+ return promise;
25
+ };
package/src/libs/util.ts CHANGED
@@ -14,8 +14,6 @@ import type {
14
14
  TProductExpanded,
15
15
  TSubscriptionExpanded,
16
16
  } from '@blocklet/payment-types';
17
- import { Hasher } from '@ocap/mcrypto';
18
- import { hexToNumber } from '@ocap/util';
19
17
  import { isEmpty, isObject } from 'lodash';
20
18
  import cloneDeep from 'lodash/cloneDeep';
21
19
  import isEqual from 'lodash/isEqual';
@@ -249,11 +247,11 @@ export function getCategoricalColors(specifier: string = '4e79a7f28e2ce1575976b7
249
247
 
250
248
  export function stringToColor(str: string) {
251
249
  const colors = getCategoricalColors();
252
-
253
- // @ts-ignore
254
- const hash = Hasher.SHA3.hash256(str).slice(-12);
255
- // @ts-ignore
256
- const index = Math.abs(hexToNumber(hash)) % colors.length;
250
+ let hash = 0;
251
+ for (let i = 0; i < str.length; i += 1) {
252
+ hash = (hash * 31 + str.charCodeAt(i)) | 0;
253
+ }
254
+ const index = Math.abs(hash) % colors.length;
257
255
  return colors[index];
258
256
  }
259
257
 
@@ -473,6 +473,10 @@ function CustomerFilter({
473
473
  e.stopPropagation();
474
474
  }}
475
475
  onClick={(e) => e.stopPropagation()}
476
+ // Stop MUI <Menu>'s built-in letter-key navigation from stealing keystrokes
477
+ // (it jumps to a MenuItem starting with the typed letter, which makes the
478
+ // search field appear to "swallow" inputs like 'a'/'w'). See #1357.
479
+ onKeyDown={(e) => e.stopPropagation()}
476
480
  />
477
481
  </Box>
478
482
  {customers.map((customer) => (
@@ -1,5 +1,4 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
- import DidAddress from '@arcblock/ux/lib/DID';
3
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
3
  import Toast from '@arcblock/ux/lib/Toast';
5
4
  import {
@@ -19,6 +18,7 @@ import { useRequest, useSetState } from 'ahooks';
19
18
  import { defaultCountries, FlagEmoji, parseCountry } from 'react-international-phone';
20
19
 
21
20
  import { useMemo } from 'react';
21
+ import SafeDidAddress from '../../../../components/safe-did-address';
22
22
  import BalanceList from '../../../../components/balance-list';
23
23
  import Copyable from '../../../../components/copyable';
24
24
  import EditCustomer from '../../../../components/customer/edit';
@@ -343,7 +343,7 @@ export default function CustomerDetail(props: { id: string }) {
343
343
  }}>
344
344
  <InfoRow
345
345
  label={t('common.did')}
346
- value={<DidAddress did={data.customer.did} chainId={livemode ? 'main' : 'beta'} showQrcode />}
346
+ value={<SafeDidAddress did={data.customer.did} chainId={livemode ? 'main' : 'beta'} showQrcode />}
347
347
  />
348
348
  <InfoRow label={t('admin.customer.name')} value={data.customer.name} />
349
349
  <InfoRow label={t('common.createdAt')} value={formatTime(data.customer.created_at)} />
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
2
  import { getDurableData } from '@arcblock/ux/lib/Datatable';
3
- import DidAddress from '@arcblock/ux/lib/DID';
4
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
5
4
  import { api, formatTime, getCustomerAvatar, Table } from '@blocklet/payment-react';
6
5
  import type { TCustomer } from '@blocklet/payment-types';
@@ -8,6 +7,7 @@ import { Avatar, CircularProgress, Stack, Typography } from '@mui/material';
8
7
  import { useLocalStorageState } from 'ahooks';
9
8
  import { useEffect, useState } from 'react';
10
9
  import { Link } from 'react-router-dom';
10
+ import SafeDidAddress from '../../../../components/safe-did-address';
11
11
 
12
12
  const fetchData = (params: Record<string, any> = {}): Promise<{ list: TCustomer[]; count: number }> => {
13
13
  const search = new URLSearchParams();
@@ -98,7 +98,7 @@ export default function CustomersList() {
98
98
  const item = data.list[index] as TCustomer;
99
99
  return (
100
100
  <Link to={`/admin/customers/${item.id}`}>
101
- <DidAddress did={item.did} />
101
+ <SafeDidAddress did={item.did} />
102
102
  </Link>
103
103
  );
104
104
  },
@@ -328,7 +328,9 @@ export default function Overview() {
328
328
  const summaryLoading = summary.loading;
329
329
  const trendLoading = trend.loading;
330
330
  const overdueLoading = overdueSummary.loading;
331
- const overdueList = overdueSummary.data?.list || [];
331
+ // Filter out items with missing currency data overdue API may return items whose
332
+ // referenced currency was deleted or failed to expand
333
+ const overdueList = (overdueSummary.data?.list || []).filter((item: any) => item?.currency?.id);
332
334
  const hasOverdue = overdueList.length > 0;
333
335
  const summaryData = summary.data;
334
336
  const summarySummary = summaryData?.summary;
@@ -284,13 +284,13 @@ export default function CustomerSubscriptionDetail() {
284
284
 
285
285
  const renderOverdraftProtectionLabel = () => {
286
286
  const enabled = data?.overdraft_protection?.enabled;
287
+ const upcomingAmount = overdraftProtection?.upcoming?.amount || '0';
288
+ const gasAmount = overdraftProtection?.gas || '0';
287
289
  const insufficient =
288
290
  !enableOverdraftProtection ||
289
291
  overdraftProtection.unused === '0' ||
290
- new BN(overdraftProtection.unused).lt(
291
- new BN(overdraftProtection.upcoming?.amount).add(new BN(overdraftProtection.gas))
292
- );
293
- const estimateAmount = +fromUnitToToken(cycleAmount.amount, data.paymentCurrency?.decimal);
292
+ new BN(overdraftProtection.unused).lt(new BN(upcomingAmount).add(new BN(gasAmount)));
293
+ const estimateAmount = +fromUnitToToken(cycleAmount.amount || '0', data.paymentCurrency?.decimal);
294
294
 
295
295
  const remainingStake = +fromUnitToToken(overdraftProtection?.unused || '0', data.paymentCurrency?.decimal);
296
296
  const formatEstimatedDuration = (cycles: number) => {
package/tsconfig.api.json CHANGED
@@ -3,12 +3,7 @@
3
3
  "compilerOptions": {
4
4
  "outDir": "api/dist",
5
5
  "noEmit": false,
6
- "noEmitOnError": true,
7
- "typeRoots": [
8
- "./node_modules/@types",
9
- "env.d.ts",
10
- "pretty-ms-i18n.d.ts"
11
- ],
6
+ "noEmitOnError": true
12
7
  },
13
8
  "include": ["api/*.d.ts", "api/src"]
14
9
  }
package/tsconfig.json CHANGED
@@ -31,11 +31,9 @@
31
31
  "module": "commonjs" /* Specify what module code is generated. */,
32
32
  // "rootDir": "./", /* Specify the root folder within your source files. */
33
33
  // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
34
- // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
35
- // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
36
34
  // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
37
35
  // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
38
- // "types": [], /* Specify type package names to be included without being referenced in a source file. */
36
+ "types": ["node", "react", "react-dom", "express", "cookie-parser", "cors", "debug", "dotenv-flow", "jest"], /* Only include types explicitly used by this package */
39
37
  // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
40
38
  // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
41
39
  // "resolveJsonModule": true, /* Enable importing .json files. */
@@ -98,5 +96,6 @@
98
96
  /* Completeness */
99
97
  // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
100
98
  "skipLibCheck": true, /* Skip type checking all .d.ts files. */
101
- }
99
+ },
100
+ "exclude": ["node_modules", "cloudflare"]
102
101
  }
@@ -5,7 +5,8 @@
5
5
  "noEmit": false,
6
6
  "declaration": true,
7
7
  "emitDeclarationOnly": true,
8
- "noEmitOnError": true
8
+ "noEmitOnError": true,
9
+ "types": ["node"]
9
10
  },
10
11
  "include": ["api/*.d.ts", "api/src/store/models"]
11
12
  }
package/vite.config.ts CHANGED
@@ -56,6 +56,7 @@ export default defineConfig(({ mode }) => {
56
56
  createBlockletPlugin({
57
57
  disableDynamicAssetHost: false,
58
58
  chunkSizeLimit: 3500,
59
+ disableNodePolyfills: true,
59
60
  }),
60
61
  svgr(),
61
62
  process.env.ANALYZE && visualizer({ open: true, gzipSize: true, brotliSize: true }),
@@ -106,6 +107,10 @@ export default defineConfig(({ mode }) => {
106
107
  },
107
108
  },
108
109
  },
110
+ define: {
111
+ global: 'globalThis',
112
+ 'process.env': {},
113
+ },
109
114
  optimizeDeps: {
110
115
  include: ['react', 'react-dom', 'react/jsx-runtime'],
111
116
  esbuildOptions: { mainFields: ['module', 'main'], resolveExtensions: ['.ts', '.tsx', '.js', '.jsx'] },
@@ -128,4 +133,4 @@ export default defineConfig(({ mode }) => {
128
133
  ],
129
134
  },
130
135
  };
131
- });
136
+ });