payment-kit 1.18.19 → 1.18.21

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.
@@ -21,6 +21,7 @@ const handleUserUpdate = async ({ user }: { user: any }) => {
21
21
  email: user.email,
22
22
  phone: user.phone,
23
23
  last_sync_at: now,
24
+ address: Customer.formatAddressFromUser(user),
24
25
  });
25
26
  logger.info(`customer info updated: ${customer.did}`);
26
27
  }
@@ -125,8 +125,8 @@ export const handlePaymentSucceed = async (
125
125
  did: user.did,
126
126
  name: user.fullName,
127
127
  email: user.email,
128
- phone: '',
129
- address: {},
128
+ phone: user.phone,
129
+ address: Customer.formatAddressFromUser(user),
130
130
  description: user.remark,
131
131
  metadata: {},
132
132
  balance: '0',
@@ -76,6 +76,7 @@ import { ensureInvoiceForCheckout } from './connect/shared';
76
76
  import { isCreditSufficientForPayment, isDelegationSufficientForPayment } from '../libs/payment';
77
77
  import { handleStripeSubscriptionSucceed } from '../integrations/stripe/handlers/subscription';
78
78
  import { CHARGE_SUPPORTED_CHAIN_TYPES } from '../libs/constants';
79
+ import { blocklet } from '../libs/auth';
79
80
 
80
81
  const router = Router();
81
82
 
@@ -838,14 +839,15 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
838
839
 
839
840
  let customer = await Customer.findOne({ where: { did: req.user.did } });
840
841
  if (!customer) {
842
+ const { user: userInfo } = await blocklet.getUser(req.user.did);
841
843
  customer = await Customer.create({
842
844
  livemode: !!checkoutSession.livemode,
843
845
  did: req.user.did,
844
846
  name: req.body.customer_name,
845
- email: req.body.customer_email,
846
- phone: req.body.customer_phone,
847
- address: req.body.billing_address,
848
- description: '',
847
+ email: req.body.customer_email || userInfo?.email || '',
848
+ phone: req.body.customer_phone || userInfo?.phone || '',
849
+ address: req.body.billing_address || Customer.formatAddressFromUser(userInfo),
850
+ description: userInfo?.remark || '',
849
851
  metadata: {},
850
852
  balance: '0',
851
853
  next_invoice_sequence: 1,
@@ -853,6 +855,20 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
853
855
  invoice_prefix: Customer.getInvoicePrefix(),
854
856
  });
855
857
  logger.info('customer created on checkout session submit', { did: req.user.did, id: customer.id });
858
+ try {
859
+ await blocklet.updateUserAddress({
860
+ did: customer.did,
861
+ address: Customer.formatAddressFromCustomer(customer),
862
+ });
863
+ logger.info('updateUserAddress success', {
864
+ did: customer.did,
865
+ });
866
+ } catch (err) {
867
+ logger.error('updateUserAddress failed', {
868
+ error: err,
869
+ customerId: customer.id,
870
+ });
871
+ }
856
872
  } else {
857
873
  const updates: Record<string, string> = {};
858
874
  if (checkoutSession.customer_update?.name) {
@@ -868,6 +884,22 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
868
884
  }
869
885
 
870
886
  await customer.update(updates);
887
+ try {
888
+ await blocklet.updateUserAddress({
889
+ did: customer.did,
890
+ address: Customer.formatAddressFromCustomer(customer),
891
+ // @ts-ignore
892
+ phone: customer.phone,
893
+ });
894
+ logger.info('updateUserAddress success', {
895
+ did: customer.did,
896
+ });
897
+ } catch (err) {
898
+ logger.error('updateUserAddress failed', {
899
+ error: err,
900
+ customerId: customer.id,
901
+ });
902
+ }
871
903
  }
872
904
 
873
905
  // check if customer can make new purchase
@@ -117,6 +117,7 @@ export async function ensurePaymentIntent(checkoutSessionId: string, userDid?: s
117
117
  metadata: { fromDonation: true },
118
118
  livemode: checkoutSession.livemode,
119
119
  phone: user.phone,
120
+ address: Customer.formatAddressFromUser(user),
120
121
  delinquent: false,
121
122
  balance: '0',
122
123
  next_invoice_sequence: 1,
@@ -115,7 +115,7 @@ router.get('/me', sessionMiddleware(), async (req, res) => {
115
115
  name: user.fullName,
116
116
  email: user.email,
117
117
  phone: user.phone,
118
- address: {},
118
+ address: Customer.formatAddressFromUser(user),
119
119
  description: user.remark,
120
120
  metadata: {},
121
121
  balance: '0',
@@ -169,7 +169,7 @@ router.get('/me', sessionMiddleware(), async (req, res) => {
169
169
  });
170
170
 
171
171
  // get overdue invoices
172
- router.get('/:id/overdue/invoices', auth, async (req, res) => {
172
+ router.get('/:id/overdue/invoices', sessionMiddleware(), async (req, res) => {
173
173
  if (!req.user) {
174
174
  return res.status(403).json({ error: 'Unauthorized' });
175
175
  }
@@ -140,7 +140,7 @@ router.post('/', async (req, res) => {
140
140
  const defaultSetting = {
141
141
  amount: {
142
142
  presets: ['1', '5', '10'],
143
- preset: '1',
143
+ preset: '5',
144
144
  minimum: '0.01',
145
145
  maximum: '100',
146
146
  custom: true,
@@ -154,7 +154,7 @@ router.post('/', async (req, res) => {
154
154
  presets: Joi.array()
155
155
  .items(Joi.string())
156
156
  .when('custom', { is: false, then: Joi.required(), otherwise: Joi.optional() }),
157
- preset: Joi.string().optional(),
157
+ preset: Joi.string().empty('').optional(),
158
158
  minimum: Joi.string().when('custom', { is: true, then: Joi.required() }),
159
159
  maximum: Joi.string().when('custom', { is: true, then: Joi.required() }),
160
160
  custom: Joi.boolean().required(),
@@ -238,7 +238,7 @@ router.put('/:mountLocationOrId', authAdmin, async (req, res) => {
238
238
  presets: Joi.array()
239
239
  .items(Joi.string())
240
240
  .when('custom', { is: false, then: Joi.required(), otherwise: Joi.optional() }),
241
- preset: Joi.string().optional(),
241
+ preset: Joi.string().empty('').optional(),
242
242
  minimum: Joi.string().when('custom', { is: true, then: Joi.required() }),
243
243
  maximum: Joi.string().when('custom', { is: true, then: Joi.required() }),
244
244
  custom: Joi.boolean().required(),
@@ -282,6 +282,28 @@ export class Customer extends Model<InferAttributes<Customer>, InferCreationAttr
282
282
  public static getInvoicePrefix() {
283
283
  return nextInvoicePrefix();
284
284
  }
285
+
286
+ public static formatAddressFromUser(user: any) {
287
+ return {
288
+ country: user.address?.country || '',
289
+ state: user.address?.province || '',
290
+ city: user.address?.city || '',
291
+ line1: user.address?.line1 || '',
292
+ line2: user.address?.line2 || '',
293
+ postal_code: user.address?.postalCode || '',
294
+ };
295
+ }
296
+
297
+ public static formatAddressFromCustomer(customer: Customer) {
298
+ return {
299
+ country: customer.address?.country || '',
300
+ province: customer.address?.state || '',
301
+ city: customer.address?.city || '',
302
+ line1: customer.address?.line1 || '',
303
+ line2: customer.address?.line2 || '',
304
+ postalCode: customer.address?.postal_code || '',
305
+ };
306
+ }
285
307
  }
286
308
 
287
309
  export type TCustomer = InferAttributes<Customer>;
package/blocklet.yml CHANGED
@@ -14,7 +14,7 @@ repository:
14
14
  type: git
15
15
  url: git+https://github.com/blocklet/payment-kit.git
16
16
  specVersion: 1.2.8
17
- version: 1.18.19
17
+ version: 1.18.21
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.18.19",
3
+ "version": "1.18.21",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -46,16 +46,16 @@
46
46
  "@abtnode/cron": "^1.16.40",
47
47
  "@arcblock/did": "^1.19.15",
48
48
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
49
- "@arcblock/did-connect": "^2.12.36",
49
+ "@arcblock/did-connect": "^2.12.43",
50
50
  "@arcblock/did-util": "^1.19.15",
51
51
  "@arcblock/jwt": "^1.19.15",
52
- "@arcblock/ux": "^2.12.36",
52
+ "@arcblock/ux": "^2.12.43",
53
53
  "@arcblock/validator": "^1.19.15",
54
54
  "@blocklet/js-sdk": "^1.16.40",
55
55
  "@blocklet/logger": "^1.16.40",
56
- "@blocklet/payment-react": "1.18.19",
56
+ "@blocklet/payment-react": "1.18.21",
57
57
  "@blocklet/sdk": "^1.16.40",
58
- "@blocklet/ui-react": "^2.12.36",
58
+ "@blocklet/ui-react": "^2.12.43",
59
59
  "@blocklet/uploader": "^0.1.79",
60
60
  "@blocklet/xss": "^0.1.30",
61
61
  "@mui/icons-material": "^5.16.6",
@@ -121,7 +121,7 @@
121
121
  "devDependencies": {
122
122
  "@abtnode/types": "^1.16.40",
123
123
  "@arcblock/eslint-config-ts": "^0.3.3",
124
- "@blocklet/payment-types": "1.18.19",
124
+ "@blocklet/payment-types": "1.18.21",
125
125
  "@types/cookie-parser": "^1.4.7",
126
126
  "@types/cors": "^2.8.17",
127
127
  "@types/debug": "^4.1.12",
@@ -151,7 +151,7 @@
151
151
  "vite": "^5.3.5",
152
152
  "vite-node": "^2.0.4",
153
153
  "vite-plugin-babel-import": "^2.0.5",
154
- "vite-plugin-blocklet": "^0.9.25",
154
+ "vite-plugin-blocklet": "^0.9.27",
155
155
  "vite-plugin-node-polyfills": "^0.21.0",
156
156
  "vite-plugin-svgr": "^4.2.0",
157
157
  "vite-tsconfig-paths": "^4.3.2",
@@ -167,5 +167,5 @@
167
167
  "parser": "typescript"
168
168
  }
169
169
  },
170
- "gitHead": "eabd58598ad349a5177dca2e6302c82117d9b687"
170
+ "gitHead": "cf08902482dded20ee91402ed6d4678383965daf"
171
171
  }
@@ -3,10 +3,19 @@ import { PaymentProvider } from '@blocklet/payment-react';
3
3
  import { UserCenter } from '@blocklet/ui-react';
4
4
  import { useEffect } from 'react';
5
5
 
6
+ import { useSearchParams } from 'react-router-dom';
6
7
  import { useSessionContext } from '../../contexts/session';
7
8
 
8
9
  export default function UserLayout(props: any) {
9
10
  const { session, connectApi, events } = useSessionContext();
11
+ const [params] = useSearchParams();
12
+ const embed = params.get('embed') || sessionStorage.getItem('embed');
13
+
14
+ useEffect(() => {
15
+ if (embed) {
16
+ sessionStorage.setItem('embed', embed);
17
+ }
18
+ }, [embed]);
10
19
 
11
20
  useEffect(() => {
12
21
  events.once('logout', () => {
@@ -28,6 +37,7 @@ export default function UserLayout(props: any) {
28
37
  currentTab={`${window.blocklet.prefix}customer`}
29
38
  userDid={session.user.did}
30
39
  hideFooter
40
+ embed={embed === '1'}
31
41
  notLoginContent="undefined">
32
42
  {props.children}
33
43
  </UserCenter>
package/src/libs/dayjs.ts CHANGED
@@ -54,8 +54,9 @@ export const formatSmartDuration = (
54
54
  { t, separator = ' ' }: FormatDurationOptions
55
55
  ): string => {
56
56
  // Format single unit
57
- const formatUnit = (val: number, unitType: TimeUnit): string =>
58
- `${val} ${t(`common.${unitType}${val > 1 ? 's' : ''}`).toLowerCase()}`;
57
+ const formatUnit = (val: number, unitType: TimeUnit): string => {
58
+ return `${val || 0} ${t(`common.${unitType}${val > 1 ? 's' : ''}`).toLowerCase()}`;
59
+ };
59
60
 
60
61
  // Convert to largest possible unit
61
62
  const convertToLargest = (val: number, fromUnit: TimeUnit): [TimeUnit, number][] => {
@@ -129,7 +130,7 @@ export const formatSmartDuration = (
129
130
 
130
131
  // Get units and filter out zero values
131
132
  const units = convertToLargest(value, unit).filter(([, val]) => val > 0);
132
-
133
+ if (units.length === 0) return formatUnit(0, unit);
133
134
  // Format all units
134
135
  return units.map(([u, val]) => formatUnit(val, u)).join(separator);
135
136
  };
@@ -35,7 +35,7 @@ import { styled } from '@mui/system';
35
35
  import { useRequest, useSetState } from 'ahooks';
36
36
  import { flatten, isEmpty } from 'lodash';
37
37
  import { memo, useEffect, useState } from 'react';
38
- import { useNavigate } from 'react-router-dom';
38
+ import { useNavigate, useSearchParams } from 'react-router-dom';
39
39
  import { joinURL } from 'ufo';
40
40
 
41
41
  import { useTransitionContext } from '../../components/progress-bar';
@@ -178,7 +178,7 @@ type CardType = 'balance' | 'spent' | 'stake' | 'refund' | 'due';
178
178
 
179
179
  // 定义一个统一的卡片配置
180
180
  const CARD_CONFIG: Record<CardType, { key: keyof typeof summaryKeyMap; alwaysShow?: boolean }> = {
181
- balance: { key: 'token', alwaysShow: true },
181
+ balance: { key: 'token' },
182
182
  spent: { key: 'paid', alwaysShow: true },
183
183
  stake: { key: 'stake' },
184
184
  refund: { key: 'refund' },
@@ -194,16 +194,22 @@ const summaryKeyMap = {
194
194
  due: 'due',
195
195
  } as const;
196
196
 
197
- // 优化后的计算函数
198
- const getCardVisibility = (currency: any, data: any) => {
197
+ const isCardVisible = (type: string, config: any, data: any, currency: any, method: any) => {
198
+ const summaryKey = config.key;
199
+ const hasSummaryValue =
200
+ data?.summary?.[summaryKey]?.[currency.id] && data?.summary?.[summaryKey]?.[currency.id] !== '0';
201
+
202
+ if (type === 'balance') {
203
+ return method?.type === 'arcblock' && (config.alwaysShow || hasSummaryValue);
204
+ }
205
+
206
+ return config.alwaysShow || hasSummaryValue;
207
+ };
208
+
209
+ const getCardVisibility = (currency: any, data: any, method: any) => {
199
210
  return Object.entries(CARD_CONFIG).reduce(
200
211
  (acc, [type, config]) => {
201
- const summaryKey = config.key;
202
- const isVisible =
203
- config.alwaysShow ||
204
- (data?.summary?.[summaryKey]?.[currency.id] && data?.summary?.[summaryKey]?.[currency.id] !== '0');
205
-
206
- if (isVisible) {
212
+ if (isCardVisible(type, config, data, currency, method)) {
207
213
  acc.visibleCards.push(type as CardType);
208
214
  }
209
215
  return acc;
@@ -213,9 +219,10 @@ const getCardVisibility = (currency: any, data: any) => {
213
219
  };
214
220
 
215
221
  // 计算最大卡片数量
216
- const calculateMaxCardCount = (currencies: any[], data: any) => {
222
+ const calculateMaxCardCount = (currencies: any[], data: any, settings: any) => {
217
223
  return currencies.reduce((maxCount, currency) => {
218
- const { visibleCards } = getCardVisibility(currency, data);
224
+ const method = settings?.paymentMethods?.find((m: any) => m.id === currency.payment_method_id);
225
+ const { visibleCards } = getCardVisibility(currency, data, method);
219
226
  return Math.max(maxCount, visibleCards.length);
220
227
  }, 0);
221
228
  };
@@ -244,6 +251,8 @@ export default function CustomerHome() {
244
251
  }))
245
252
  )
246
253
  );
254
+ const [params] = useSearchParams();
255
+ const embed = params.get('embed') || sessionStorage.getItem('embed');
247
256
 
248
257
  const { livemode, setLivemode } = usePaymentContext();
249
258
  const [state, setState] = useSetState({
@@ -378,7 +387,7 @@ export default function CustomerHome() {
378
387
  </Box>
379
388
  );
380
389
 
381
- const maxCardCount = calculateMaxCardCount(currencies, data);
390
+ const maxCardCount = calculateMaxCardCount(currencies, data, settings);
382
391
 
383
392
  const responsiveColumns = {
384
393
  xs: Math.min(2, maxCardCount),
@@ -397,6 +406,10 @@ export default function CustomerHome() {
397
406
  </Box>
398
407
  <Stack gap={2} mt={2}>
399
408
  {currencies.map((c) => {
409
+ const method = settings?.paymentMethods?.find((m) => m.id === c.payment_method_id);
410
+ if (method?.type !== 'arcblock') {
411
+ return null;
412
+ }
400
413
  return isSummaryEmpty(data?.summary, c.id) && c.id !== settings.baseCurrency.id ? null : (
401
414
  <Stack
402
415
  key={c.id}
@@ -444,12 +457,9 @@ export default function CustomerHome() {
444
457
  }}>
445
458
  {/* 使用配置渲染卡片 */}
446
459
  {Object.entries(CARD_CONFIG).map(([type, config]) => {
447
- const summaryKey = config.key;
448
- const isVisible =
449
- config.alwaysShow ||
450
- (data?.summary?.[summaryKey]?.[c.id] && data?.summary?.[summaryKey]?.[c.id] !== '0');
451
-
452
- if (!isVisible) return null;
460
+ if (!isCardVisible(type, config, data, c, method)) {
461
+ return null;
462
+ }
453
463
 
454
464
  return (
455
465
  <Box
@@ -467,7 +477,6 @@ export default function CustomerHome() {
467
477
  backgroundColor: 'var(--stroke-border-base, #EFF1F5)',
468
478
  zIndex: 1,
469
479
  },
470
-
471
480
  '&:has(+ .placeholder)::after': {
472
481
  display: 'none',
473
482
  },
@@ -477,7 +486,7 @@ export default function CustomerHome() {
477
486
  }}>
478
487
  <CurrencyCard
479
488
  label={t(`admin.customer.summary.${type}`)}
480
- data={data?.summary?.[summaryKey] || emptyObject}
489
+ data={data?.summary?.[config.key] || emptyObject}
481
490
  currency={c}
482
491
  type={type as CardType}
483
492
  icon={getCardIcon(type as CardType)}
@@ -501,14 +510,14 @@ export default function CustomerHome() {
501
510
  <Box className="base-card section section-invoices">
502
511
  <Box className="section-header">
503
512
  <Typography variant="h3">{t('customer.invoiceHistory')}</Typography>
504
- {isEmpty(data?.summary?.due) === false && (
513
+ {isEmpty(data?.summary?.due) !== false && (
505
514
  <Tooltip title={t('payment.customer.pastDue.warning')}>
506
515
  <Button
507
516
  variant="text"
508
517
  color="error"
509
518
  component="a"
510
519
  size="small"
511
- href={joinURL(getPrefix(), '/customer/invoice/past-due')}
520
+ href={joinURL(getPrefix(), '/customer/invoice/past-due', `${embed === '1' ? '?embed=1' : ''}`)}
512
521
  target="_blank"
513
522
  rel="noreferrer"
514
523
  style={{ textDecoration: 'none', fontSize: 13 }}>
@@ -83,6 +83,15 @@ export default function BalanceRechargePage() {
83
83
  const [loading, setLoading] = useState(true);
84
84
  const [error, setError] = useState('');
85
85
  const customInputRef = useRef<HTMLInputElement>(null);
86
+ const [unitCycle, setUnitCycle] = useState<{
87
+ amount: string;
88
+ interval: TimeUnit;
89
+ cycle: number;
90
+ }>({
91
+ amount: '0',
92
+ interval: 'month',
93
+ cycle: 1,
94
+ });
86
95
  const [payerValue, setPayerValue] = useState<{
87
96
  paymentAddress: string;
88
97
  token: string;
@@ -108,9 +117,14 @@ export default function BalanceRechargePage() {
108
117
  setCurrency(data.currency);
109
118
  setRelatedSubscriptions(data.relatedSubscriptions || []);
110
119
 
111
- if (data.recommendedRecharge && data.recommendedRecharge.amount) {
120
+ if (data.recommendedRecharge && data.recommendedRecharge.amount && data.recommendedRecharge.amount !== '0') {
112
121
  const baseAmount = data.recommendedRecharge.amount;
113
122
  const decimal = data.currency.decimal || 0;
123
+ setUnitCycle({
124
+ amount: parseFloat(formatBNStr(baseAmount, decimal, 6, true)).toString(),
125
+ interval: data.recommendedRecharge.interval as TimeUnit,
126
+ cycle: data.recommendedRecharge.cycle,
127
+ });
114
128
  setPresetAmounts([
115
129
  {
116
130
  amount: Math.ceil(parseFloat(formatBNStr(baseAmount, decimal, 6, true))).toString(),
@@ -494,6 +508,19 @@ export default function BalanceRechargePage() {
494
508
  }}
495
509
  inputRef={customInputRef}
496
510
  />
511
+ {amount && Number(amount) > 0 && Number(unitCycle.amount) > 0 && !amountError && (
512
+ <Typography variant="body2" sx={{ color: 'text.lighter', mt: 1 }} fontSize={12}>
513
+ {t('common.estimatedDuration', {
514
+ duration: formatSmartDuration(
515
+ Math.floor(Number(amount) / Number(unitCycle.amount)) < 1
516
+ ? parseFloat((Number(amount) / Number(unitCycle.amount)).toFixed(1))
517
+ : Math.floor(Number(amount) / Number(unitCycle.amount)),
518
+ unitCycle.interval,
519
+ { t }
520
+ ),
521
+ })}
522
+ </Typography>
523
+ )}
497
524
  </Box>
498
525
  )}
499
526
 
@@ -479,7 +479,11 @@ export default function RechargePage() {
479
479
  />
480
480
  {amount && Number(amount) > 0 && Number(cycleAmount) > 0 && !amountError && (
481
481
  <Typography variant="body2" sx={{ color: 'text.lighter', mt: '8px !important' }} fontSize={12}>
482
- {formatEstimatedDuration(Math.floor(Number(amount) / Number(cycleAmount)))}
482
+ {formatEstimatedDuration(
483
+ Math.floor(Number(amount) / Number(cycleAmount)) < 1
484
+ ? parseFloat((Number(amount) / Number(cycleAmount)).toFixed(1))
485
+ : Math.floor(Number(amount) / Number(cycleAmount))
486
+ )}
483
487
  </Typography>
484
488
  )}
485
489
  </Box>