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.
- package/__blocklet__.js +37 -0
- package/api/ocap-1.30-subpath-shims.d.ts +35 -0
- package/api/src/crons/index.ts +10 -0
- package/api/src/crons/metering-subscription-detection.ts +12 -14
- package/api/src/crons/overdue-detection.ts +51 -74
- package/api/src/integrations/arcblock/nft.ts +6 -2
- package/api/src/integrations/arcblock/stake.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +4 -4
- package/api/src/integrations/blocklet/notification.ts +1 -1
- package/api/src/integrations/ethereum/tx.ts +29 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +70 -53
- package/api/src/integrations/stripe/handlers/payment-intent.ts +8 -1
- package/api/src/integrations/stripe/resource.ts +8 -0
- package/api/src/libs/audit.ts +32 -16
- package/api/src/libs/auth.ts +49 -2
- package/api/src/libs/chain-error.ts +31 -0
- package/api/src/libs/error.ts +15 -0
- package/api/src/libs/event.ts +42 -1
- package/api/src/libs/invoice.ts +69 -34
- package/api/src/libs/notification/template/customer-auto-recharge-daily-limit-exceeded.ts +1 -3
- package/api/src/libs/notification/template/customer-auto-recharge-failed.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-grant-granted.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +1 -3
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -3
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -3
- package/api/src/libs/notification/template/one-time-payment-refund-succeeded.ts +1 -3
- package/api/src/libs/notification/template/one-time-payment-succeeded.ts +1 -3
- package/api/src/libs/notification/template/subscription-renew-failed.ts +1 -3
- package/api/src/libs/notification/template/subscription-slippage-exceeded.ts +1 -3
- package/api/src/libs/notification/template/subscription-slippage-warning.ts +1 -3
- package/api/src/libs/notification/template/subscription-succeeded.ts +1 -1
- package/api/src/libs/pagination.ts +14 -9
- package/api/src/libs/payment.ts +25 -10
- package/api/src/libs/session.ts +1 -1
- package/api/src/libs/timing.ts +35 -0
- package/api/src/libs/util.ts +16 -15
- package/api/src/libs/wallet-migration.ts +72 -53
- package/api/src/queues/auto-recharge.ts +1 -1
- package/api/src/queues/credit-consume.ts +94 -12
- package/api/src/queues/credit-grant.ts +4 -0
- package/api/src/queues/event.ts +14 -2
- package/api/src/queues/invoice.ts +1 -0
- package/api/src/queues/payment.ts +83 -15
- package/api/src/queues/refund.ts +84 -71
- package/api/src/queues/subscription.ts +1 -0
- package/api/src/routes/checkout-sessions.ts +82 -43
- package/api/src/routes/connect/change-payment.ts +2 -0
- package/api/src/routes/connect/change-plan.ts +2 -0
- package/api/src/routes/connect/pay.ts +12 -3
- package/api/src/routes/connect/setup.ts +3 -1
- package/api/src/routes/connect/shared.ts +52 -39
- package/api/src/routes/connect/subscribe.ts +4 -1
- package/api/src/routes/credit-grants.ts +25 -17
- package/api/src/routes/donations.ts +2 -2
- package/api/src/routes/meter-events.ts +16 -6
- package/api/src/routes/payment-links.ts +1 -1
- package/api/src/routes/payment-methods.ts +1 -1
- package/api/src/routes/settings.ts +1 -1
- package/api/src/routes/tax-rates.ts +1 -1
- package/api/src/store/models/customer.ts +23 -1
- package/api/src/store/models/payment-method.ts +4 -0
- package/api/src/store/models/price.ts +23 -14
- package/api/tests/libs/wallet-migration.spec.ts +4 -4
- package/api/tests/queues/credit-consume-batch.spec.ts +5 -2
- package/api/tests/queues/credit-consume.spec.ts +8 -4
- package/api/tests/routes/credit-grants.spec.ts +1 -0
- package/blocklet.yml +1 -1
- package/cloudflare/MIGRATION-CHALLENGES.md +676 -0
- package/cloudflare/MIGRATION-RUNBOOK.md +777 -0
- package/cloudflare/README.md +499 -0
- package/cloudflare/STAGING-MIGRATION-GUIDE.md +602 -0
- package/cloudflare/build.ts +151 -0
- package/cloudflare/did-connect-auth.ts +527 -0
- package/cloudflare/docs/2026-04-22-sdk-1.30.9-upgrade-retro.md +324 -0
- package/cloudflare/docs/2026-04-24-queue-ops-followup.md +218 -0
- package/cloudflare/docs/cf-queues-ops-alert-analysis.md +663 -0
- package/cloudflare/docs/cf-workers-local-dev-and-fixes.md +284 -0
- package/cloudflare/docs/cleanup-tasks-2026-05.md +62 -0
- package/cloudflare/docs/payment-kit-platform-analysis-2026-04-20.md +354 -0
- package/cloudflare/frontend-shims/buffer-polyfill.ts +9 -0
- package/cloudflare/frontend-shims/js-sdk.ts +43 -0
- package/cloudflare/frontend-shims/mime-types.ts +46 -0
- package/cloudflare/frontend-shims/session.ts +24 -0
- package/cloudflare/frontend-shims/vite-plugin-noop.ts +6 -0
- package/cloudflare/index.html +40 -0
- package/cloudflare/migrate-to-d1.js +252 -0
- package/cloudflare/migrations/0001_initial_schema.sql +82 -0
- package/cloudflare/migrations/0002_indexes.sql +75 -0
- package/cloudflare/migrations/0003_locks_and_constraints.sql +18 -0
- package/cloudflare/run-build.js +390 -0
- package/cloudflare/scripts/test-decrypt.js +102 -0
- package/cloudflare/shims/arcblock-ws.ts +20 -0
- package/cloudflare/shims/axios-http-adapter.ts +4 -0
- package/cloudflare/shims/axios-lite.ts +117 -0
- package/cloudflare/shims/blocklet-sdk/auth-service.ts +33 -0
- package/cloudflare/shims/blocklet-sdk/cdn.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/component-api.ts +35 -0
- package/cloudflare/shims/blocklet-sdk/component.ts +18 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/did.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/env.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/eventbus.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/fallback.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/index.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/logger.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/middlewares.ts +15 -0
- package/cloudflare/shims/blocklet-sdk/notification.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/security.ts +53 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/verify-sign.ts +38 -0
- package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +6 -0
- package/cloudflare/shims/blocklet-sdk/wallet.ts +103 -0
- package/cloudflare/shims/cookie-parser.ts +3 -0
- package/cloudflare/shims/cors.ts +21 -0
- package/cloudflare/shims/cron.ts +189 -0
- package/cloudflare/shims/crypto-js-warn.ts +7 -0
- package/cloudflare/shims/did-space-js.ts +17 -0
- package/cloudflare/shims/did-space.ts +11 -0
- package/cloudflare/shims/error.ts +18 -0
- package/cloudflare/shims/express-compat/index.ts +80 -0
- package/cloudflare/shims/express-compat/types.ts +41 -0
- package/cloudflare/shims/fastq.ts +105 -0
- package/cloudflare/shims/lock.ts +115 -0
- package/cloudflare/shims/mime-types.ts +56 -0
- package/cloudflare/shims/nedb-storage.ts +9 -0
- package/cloudflare/shims/node-child-process.ts +9 -0
- package/cloudflare/shims/node-fs.ts +20 -0
- package/cloudflare/shims/node-http.ts +13 -0
- package/cloudflare/shims/node-https.ts +4 -0
- package/cloudflare/shims/node-misc.ts +15 -0
- package/cloudflare/shims/node-net.ts +8 -0
- package/cloudflare/shims/node-os.ts +14 -0
- package/cloudflare/shims/node-tty.ts +8 -0
- package/cloudflare/shims/node-zlib.ts +17 -0
- package/cloudflare/shims/noop.ts +26 -0
- package/cloudflare/shims/payment-vendor.ts +14 -0
- package/cloudflare/shims/querystring.ts +12 -0
- package/cloudflare/shims/queue.ts +585 -0
- package/cloudflare/shims/rolldown-runtime.ts +43 -0
- package/cloudflare/shims/sequelize-d1/datatypes.ts +24 -0
- package/cloudflare/shims/sequelize-d1/helpers.ts +46 -0
- package/cloudflare/shims/sequelize-d1/index.ts +34 -0
- package/cloudflare/shims/sequelize-d1/model.ts +1157 -0
- package/cloudflare/shims/sequelize-d1/operators.ts +293 -0
- package/cloudflare/shims/sequelize-d1/retry.ts +85 -0
- package/cloudflare/shims/sequelize-d1/sequelize-class.ts +119 -0
- package/cloudflare/shims/sequelize-d1/timing.ts +81 -0
- package/cloudflare/shims/sequelize-d1/types.ts +35 -0
- package/cloudflare/shims/stripe-cf.ts +29 -0
- package/cloudflare/shims/ws-lite.ts +103 -0
- package/cloudflare/shims/xss.ts +3 -0
- package/cloudflare/tests/shims/cron.spec.ts +210 -0
- package/cloudflare/tests/shims/queue-scheduled.spec.ts +186 -0
- package/cloudflare/vite.config.ts +162 -0
- package/cloudflare/worker.ts +1553 -0
- package/cloudflare/wrangler.json +63 -0
- package/cloudflare/wrangler.jsonc +69 -0
- package/cloudflare/wrangler.staging.json +66 -0
- package/cloudflare/wrangler.toml +28 -0
- package/jest.config.js +4 -12
- package/package.json +26 -22
- package/src/app.tsx +62 -4
- package/src/components/customer/link.tsx +9 -13
- package/src/components/customer/notification-preference.tsx +3 -2
- package/src/components/filter-toolbar.tsx +4 -0
- package/src/components/invoice/list.tsx +9 -1
- package/src/components/invoice-pdf/utils.ts +2 -1
- package/src/components/layout/admin.tsx +39 -5
- package/src/components/layout/user-cf.tsx +77 -0
- package/src/components/payment-intent/actions.tsx +23 -3
- package/src/components/safe-did-address.tsx +75 -0
- package/src/libs/patch-user-card.ts +25 -0
- package/src/libs/util.ts +5 -7
- package/src/pages/admin/billing/meter-events/index.tsx +4 -0
- package/src/pages/admin/customers/customers/detail.tsx +2 -2
- package/src/pages/admin/customers/customers/index.tsx +2 -2
- package/src/pages/admin/overview.tsx +3 -1
- package/src/pages/customer/subscription/detail.tsx +4 -4
- package/tsconfig.api.json +1 -6
- package/tsconfig.json +3 -4
- package/tsconfig.types.json +2 -1
- 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,
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const index = Math.abs(
|
|
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={<
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
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
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
|
-
|
|
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
|
}
|
package/tsconfig.types.json
CHANGED
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
|
+
});
|