payment-kit 1.18.16 → 1.18.17

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.
@@ -13,7 +13,7 @@ import { encodeApproveItx } from '../../integrations/ethereum/token';
13
13
  import { blocklet, ethWallet, wallet } from '../../libs/auth';
14
14
  import logger from '../../libs/logger';
15
15
  import { getGasPayerExtra, getTokenLimitsForDelegation } from '../../libs/payment';
16
- import { getFastCheckoutAmount, getStatementDescriptor } from '../../libs/session';
16
+ import { getFastCheckoutAmount, getStatementDescriptor, isDonationCheckoutSession } from '../../libs/session';
17
17
  import {
18
18
  expandSubscriptionItems,
19
19
  getSubscriptionCreateSetup,
@@ -59,7 +59,7 @@ export async function ensureCheckoutSession(checkoutSessionId: string) {
59
59
  return checkoutSession;
60
60
  }
61
61
 
62
- export async function ensurePaymentIntent(checkoutSessionId: string, userDid?: string): Promise<Result> {
62
+ export async function ensurePaymentIntent(checkoutSessionId: string, userDid?: string, skipCustomer?: boolean): Promise<Result> {
63
63
  const checkoutSession = await ensureCheckoutSession(checkoutSessionId);
64
64
 
65
65
  let paymentCurrencyId;
@@ -98,12 +98,69 @@ export async function ensurePaymentIntent(checkoutSessionId: string, userDid?: s
98
98
  paymentCurrencyId = subscription.currency_id;
99
99
  paymentMethodId = subscription.default_payment_method_id;
100
100
  }
101
-
102
- const customer = await Customer.findByPk(checkoutSession.customer_id);
101
+ let customer = null;
102
+ if (!skipCustomer) {
103
+ // 检查是否为打赏场景
104
+ const isDonation = isDonationCheckoutSession(checkoutSession);
105
+
106
+ // if donation, create customer if not exists
107
+ if (isDonation && !checkoutSession.customer_id && userDid) {
108
+ customer = await Customer.findByPkOrDid(userDid);
109
+ if (!customer) {
110
+ const { user } = await blocklet.getUser(userDid);
111
+ if (user) {
112
+ customer = await Customer.create({
113
+ did: userDid,
114
+ email: user.email,
115
+ name: user.fullName || userDid,
116
+ description: user.remark,
117
+ metadata: { fromDonation: true },
118
+ livemode: checkoutSession.livemode,
119
+ phone: user.phone,
120
+ delinquent: false,
121
+ balance: '0',
122
+ next_invoice_sequence: 1,
123
+ invoice_prefix: Customer.getInvoicePrefix(),
124
+ });
125
+ logger.info('Customer created for donation', { userDid, customerId: customer.id });
126
+ } else {
127
+ customer = await Customer.create({
128
+ did: userDid,
129
+ email: '',
130
+ name: 'anonymous',
131
+ description: 'Anonymous customer',
132
+ metadata: { fromDonation: true, anonymous: true },
133
+ livemode: checkoutSession.livemode,
134
+ phone:'',
135
+ delinquent: false,
136
+ balance: '0',
137
+ next_invoice_sequence: 1,
138
+ invoice_prefix: Customer.getInvoicePrefix(),
139
+ });
140
+ }
141
+ }
142
+
143
+ if (customer) {
144
+ await checkoutSession.update({ customer_id: customer.id, customer_did: customer.did });
145
+ if (paymentIntent) {
146
+ await paymentIntent.update({ customer_id: customer.id });
147
+ }
148
+ logger.info('Customer associated with donation', {
149
+ userDid,
150
+ customerId: customer.id,
151
+ checkoutSessionId: checkoutSession.id,
152
+ paymentIntentId: paymentIntent?.id
153
+ });
154
+ }
155
+ } else {
156
+ // 非打赏场景或已有客户ID的情况
157
+ customer = await Customer.findByPk(checkoutSession.customer_id);
158
+ }
103
159
  if (!customer) {
104
160
  throw new Error('Customer not found');
105
161
  }
106
- if (userDid) {
162
+
163
+ if (userDid && !isDonation) {
107
164
  const { user } = await blocklet.getUser(userDid, { enableConnectedAccount: true });
108
165
  if (!user) {
109
166
  throw new Error('Seems you have not connected to this app before');
@@ -113,6 +170,8 @@ export async function ensurePaymentIntent(checkoutSessionId: string, userDid?: s
113
170
  throw new Error('This is not your payment intent');
114
171
  }
115
172
  }
173
+ }
174
+
116
175
 
117
176
  const [paymentMethod, paymentCurrency] = await Promise.all([
118
177
  PaymentMethod.findByPk(paymentMethodId),
@@ -134,7 +193,7 @@ export async function ensurePaymentIntent(checkoutSessionId: string, userDid?: s
134
193
  return {
135
194
  checkoutSession,
136
195
  paymentIntent,
137
- customer,
196
+ customer: customer as Customer,
138
197
  subscription,
139
198
  paymentMethod,
140
199
  paymentCurrency,
@@ -114,7 +114,7 @@ router.get('/me', sessionMiddleware(), async (req, res) => {
114
114
  did: req.user.did,
115
115
  name: user.fullName,
116
116
  email: user.email,
117
- phone: '',
117
+ phone: user.phone,
118
118
  address: {},
119
119
  description: user.remark,
120
120
  metadata: {},
@@ -145,7 +145,7 @@ router.get('/', async (req, res) => {
145
145
  ],
146
146
  order: [['created_at', 'DESC']],
147
147
  offset: (page - 1) * pageSize,
148
- include: [{ model: Customer, as: 'customer', attributes: ['id', 'did', 'name'] }],
148
+ include: [{ model: Customer, as: 'customer', attributes: ['id', 'did', 'name', 'metadata'] }],
149
149
  limit: pageSize,
150
150
  });
151
151
 
@@ -143,7 +143,7 @@ router.get('/', authMine, async (req, res) => {
143
143
  { model: PaymentCurrency, as: 'paymentCurrency' },
144
144
  { model: PaymentMethod, as: 'paymentMethod' },
145
145
  // { model: PaymentIntent, as: 'paymentIntent' },
146
- // { model: Subscription, as: 'subscription' },
146
+ { model: Subscription, as: 'subscription', attributes: ['id', 'description'] },
147
147
  { model: Customer, as: 'customer' },
148
148
  ],
149
149
  });
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.16
17
+ version: 1.18.17
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
@@ -48,6 +48,7 @@ interfaces:
48
48
  - /methods/**
49
49
  - /currencies/**
50
50
  blockUnauthorized: false
51
+ proxyBehavior: service
51
52
  community: ''
52
53
  documentation: ''
53
54
  homepage: ''
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.18.16",
3
+ "version": "1.18.17",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -43,21 +43,21 @@
43
43
  ]
44
44
  },
45
45
  "dependencies": {
46
- "@abtnode/cron": "^1.16.39",
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.17",
49
+ "@arcblock/did-connect": "^2.12.25",
50
50
  "@arcblock/did-util": "^1.19.15",
51
51
  "@arcblock/jwt": "^1.19.15",
52
- "@arcblock/ux": "^2.12.17",
52
+ "@arcblock/ux": "^2.12.25",
53
53
  "@arcblock/validator": "^1.19.15",
54
- "@blocklet/js-sdk": "^1.16.39",
55
- "@blocklet/logger": "^1.16.39",
56
- "@blocklet/payment-react": "1.18.16",
57
- "@blocklet/sdk": "^1.16.39",
58
- "@blocklet/ui-react": "^2.12.17",
59
- "@blocklet/uploader": "^0.1.71",
60
- "@blocklet/xss": "^0.1.27",
54
+ "@blocklet/js-sdk": "^1.16.40",
55
+ "@blocklet/logger": "^1.16.40",
56
+ "@blocklet/payment-react": "1.18.17",
57
+ "@blocklet/sdk": "^1.16.40",
58
+ "@blocklet/ui-react": "^2.12.25",
59
+ "@blocklet/uploader": "^0.1.76",
60
+ "@blocklet/xss": "^0.1.28",
61
61
  "@mui/icons-material": "^5.16.6",
62
62
  "@mui/lab": "^5.0.0-alpha.173",
63
63
  "@mui/material": "^5.16.6",
@@ -89,7 +89,7 @@
89
89
  "google-libphonenumber": "^3.2.38",
90
90
  "html2canvas": "^1.4.1",
91
91
  "iframe-resizer-react": "^1.1.1",
92
- "joi": "^17.13.3",
92
+ "joi": "^17.12.2",
93
93
  "json-stable-stringify": "^1.1.1",
94
94
  "jspdf": "^2.5.2",
95
95
  "lodash": "^4.17.21",
@@ -119,9 +119,9 @@
119
119
  "web3": "^4.16.0"
120
120
  },
121
121
  "devDependencies": {
122
- "@abtnode/types": "^1.16.39",
122
+ "@abtnode/types": "^1.16.40",
123
123
  "@arcblock/eslint-config-ts": "^0.3.3",
124
- "@blocklet/payment-types": "1.18.16",
124
+ "@blocklet/payment-types": "1.18.17",
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.24",
154
+ "vite-plugin-blocklet": "^0.9.25",
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": "676b921c6fc399d3a031b848bfbe0fbffee430a1"
170
+ "gitHead": "9d43381dd3744506455547260f5e621c6ad88e8f"
171
171
  }
@@ -10,6 +10,7 @@ type Props = {
10
10
  variant?: LiteralUnion<'square' | 'rounded' | 'circular', string>;
11
11
  sx?: SxProps;
12
12
  className?: string;
13
+ logoName?: string;
13
14
  };
14
15
 
15
16
  export default function InfoCard(props: Props) {
@@ -18,7 +19,7 @@ export default function InfoCard(props: Props) {
18
19
  return (
19
20
  <Stack direction="row" alignItems="center" spacing={1} className={`info-card-wrapper ${props.className}`}>
20
21
  {props.logo ? (
21
- <Avatar src={props.logo} alt={avatarName} variant={props.variant as any} sx={dimensions} />
22
+ <Avatar src={props.logo} alt={props.logoName ?? avatarName} variant={props.variant as any} sx={dimensions} />
22
23
  ) : (
23
24
  <Avatar variant={props.variant as any} sx={dimensions}>
24
25
  {avatarName?.slice(0, 1)}
@@ -47,4 +48,5 @@ InfoCard.defaultProps = {
47
48
  variant: 'rounded',
48
49
  sx: {},
49
50
  className: '',
51
+ logoName: '',
50
52
  };
@@ -797,7 +797,7 @@ export default flat({
797
797
  intervals: 'intervals',
798
798
  history: 'Fund History',
799
799
  relatedSubscriptions: 'Related Subscriptions',
800
- rechargeTooltip: 'Click to recharge balance',
800
+ rechargeTooltip: 'Click to add funds',
801
801
  },
802
802
  delegation: {
803
803
  title:
@@ -112,6 +112,8 @@ export default function PayoutDetail(props: { id: string }) {
112
112
  return data.destination;
113
113
  };
114
114
 
115
+ const isAnonymousPayer = paymentIntent?.customer?.metadata?.anonymous;
116
+
115
117
  return (
116
118
  <Root direction="column" spacing={2.5} mb={4}>
117
119
  <Box>
@@ -206,23 +208,32 @@ export default function PayoutDetail(props: { id: string }) {
206
208
  : '',
207
209
  48
208
210
  )}
211
+ logoName={paymentIntent?.customer?.metadata?.anonymous ? '' : paymentIntent?.customer?.name}
209
212
  name={
210
213
  <Typography
211
214
  variant="subtitle2"
212
215
  sx={{
213
- cursor: 'pointer',
214
- '&:hover': {
215
- color: 'text.link',
216
- },
216
+ ...(isAnonymousPayer
217
+ ? {}
218
+ : {
219
+ cursor: 'pointer',
220
+ '&:hover': {
221
+ color: 'text.link',
222
+ },
223
+ }),
217
224
  }}
218
225
  onClick={() => {
226
+ if (isAnonymousPayer) {
227
+ return;
228
+ }
219
229
  const url = getCustomerProfileUrl({
220
230
  userDid: paymentIntent?.customer?.did,
221
231
  locale: 'zh',
222
232
  });
223
233
  window.open(url, '_blank');
224
234
  }}>
225
- {paymentIntent?.customer?.name} ({paymentIntent?.customer?.email})
235
+ {paymentIntent?.customer?.name}
236
+ {paymentIntent?.customer?.email ? ` (${paymentIntent?.customer?.email})` : ''}
226
237
  </Typography>
227
238
  }
228
239
  description={
@@ -8,7 +8,15 @@ import {
8
8
  OverdueInvoicePayment,
9
9
  } from '@blocklet/payment-react';
10
10
  import type { GroupedBN, TCustomerExpanded } from '@blocklet/payment-types';
11
- import { ExpandMore, AccountBalanceWalletOutlined } from '@mui/icons-material';
11
+ import {
12
+ ExpandMore,
13
+ AccountBalanceWalletOutlined,
14
+ AddOutlined,
15
+ CreditCardOutlined,
16
+ AccountBalanceOutlined,
17
+ InfoOutlined,
18
+ AssignmentReturnOutlined,
19
+ } from '@mui/icons-material';
12
20
  import {
13
21
  Avatar,
14
22
  Box,
@@ -23,7 +31,7 @@ import {
23
31
  Alert,
24
32
  } from '@mui/material';
25
33
  import type { SelectChangeEvent } from '@mui/material/Select';
26
- import { styled, SxProps } from '@mui/system';
34
+ import { styled } from '@mui/system';
27
35
  import { useRequest, useSetState } from 'ahooks';
28
36
  import { flatten, isEmpty } from 'lodash';
29
37
  import { memo, useEffect, useState } from 'react';
@@ -49,19 +57,20 @@ const CurrencyCard = memo(
49
57
  label,
50
58
  data,
51
59
  currency,
52
- sx,
53
60
  type,
61
+ icon,
54
62
  }: {
55
63
  label: string;
56
64
  data: {
57
65
  [key: string]: string;
58
66
  };
67
+ icon: any;
59
68
  currency: {
60
69
  id: string;
61
70
  decimal: number;
62
71
  payment_method_id: string;
72
+ symbol: string;
63
73
  };
64
- sx: SxProps;
65
74
  type: 'balance' | 'spent' | 'stake' | 'refund' | 'due';
66
75
  }) => {
67
76
  const navigate = useNavigate();
@@ -84,17 +93,19 @@ const CurrencyCard = memo(
84
93
  return (
85
94
  <Box
86
95
  sx={{
87
- padding: '12px 16px',
88
- borderRadius: 'var(--radius-m, 8px)',
89
96
  transition: 'all 0.2s ease-in-out',
90
97
  position: 'relative',
91
- ...sx,
98
+ borderRight: '1px solid var(--stroke-border-base, #EFF1F5)',
92
99
  }}>
93
- <Typography variant="h4" gutterBottom>
100
+ <Typography
101
+ variant="body1"
102
+ gutterBottom
103
+ sx={{ color: 'text.secondary', display: 'flex', alignItems: 'center', gap: 1 }}>
104
+ {icon}
94
105
  {label}
95
106
  </Typography>
96
- <Typography variant="h3" gutterBottom>
97
- {value}
107
+ <Typography variant="h3" gutterBottom sx={{ color: 'text.primary', whiteSpace: 'nowrap' }}>
108
+ {value} {currency.symbol}
98
109
  </Typography>
99
110
  {type === 'balance' && method?.type === 'arcblock' && (
100
111
  <Tooltip title={t('customer.recharge.rechargeTooltip')} arrow placement="top">
@@ -102,7 +113,7 @@ const CurrencyCard = memo(
102
113
  onClick={handleCardClick}
103
114
  sx={{
104
115
  position: 'absolute',
105
- top: '12px',
116
+ top: 0,
106
117
  right: '12px',
107
118
  display: 'flex',
108
119
  alignItems: 'center',
@@ -111,8 +122,12 @@ const CurrencyCard = memo(
111
122
  padding: '4px',
112
123
  zIndex: 1,
113
124
  cursor: 'pointer',
125
+ color: 'text.secondary',
126
+ '&:hover': {
127
+ color: 'text.link',
128
+ },
114
129
  }}>
115
- <AccountBalanceWalletOutlined fontSize="small" />
130
+ <AddOutlined fontSize="small" />
116
131
  </Box>
117
132
  </Tooltip>
118
133
  )}
@@ -165,7 +180,7 @@ export default function CustomerHome() {
165
180
  });
166
181
  const navigate = useNavigate();
167
182
  const [subscriptionStatus, setSubscriptionStatus] = useState(false);
168
- const [hasSubscriptions, setHasSubscriptions] = useState(true);
183
+ const [hasSubscriptions, setHasSubscriptions] = useState(false);
169
184
  const { startTransition } = useTransitionContext();
170
185
  const {
171
186
  data,
@@ -174,6 +189,24 @@ export default function CustomerHome() {
174
189
  loading = true,
175
190
  } = useRequest(fetchData, {
176
191
  manual: true,
192
+ onSuccess: async (res) => {
193
+ if (!hasSubscriptions) {
194
+ const search = new URLSearchParams();
195
+ const searchParams = {
196
+ page: 1,
197
+ pageSize: 5,
198
+ showTotalCount: true,
199
+ customer_id: res.id,
200
+ };
201
+ Object.entries(searchParams).forEach(([key, value]) => {
202
+ search.set(key, String(value));
203
+ });
204
+ const { data: subscriptions } = await api.get(`/api/subscriptions?${search.toString()}`);
205
+ if (subscriptions.totalCount > 0 || subscriptions.count > 0) {
206
+ setHasSubscriptions(true);
207
+ }
208
+ }
209
+ },
177
210
  });
178
211
 
179
212
  const loadingCard = loading || !data;
@@ -322,50 +355,64 @@ export default function CustomerHome() {
322
355
  className="section-body"
323
356
  flexDirection="row"
324
357
  sx={{
325
- gap: 2,
358
+ gap: 3,
326
359
  mt: 1.5,
327
360
  flexWrap: 'wrap',
361
+ position: 'relative',
328
362
  '>div': {
329
- flex: 1,
363
+ flex: '1',
364
+ '@media (max-width: 1100px)': {
365
+ flex: '0 0 calc(50% - 12px)',
366
+ maxWidth: 'calc(50% - 12px)',
367
+ },
368
+ },
369
+ '&::after': {
370
+ content: '""',
371
+ position: 'absolute',
372
+ right: 0,
373
+ top: 0,
374
+ bottom: 0,
375
+ width: '1px',
376
+ backgroundColor: 'background.paper',
377
+ zIndex: 1,
330
378
  },
331
379
  }}>
332
380
  <CurrencyCard
333
381
  label={t('admin.customer.summary.balance')}
334
382
  data={data?.summary?.token || emptyObject}
335
383
  currency={currency}
336
- sx={{
337
- background: 'var(--tags-tag-orange-bg, #B7FEE3)',
338
- color: 'var(--tags-tag-orange-text, #007C52)',
339
- }}
340
384
  type="balance"
385
+ icon={<AccountBalanceWalletOutlined color="success" fontSize="small" />}
341
386
  />
342
387
  <CurrencyCard
343
388
  label={t('admin.customer.summary.spent')}
344
389
  data={data?.summary?.paid || emptyObject}
345
390
  currency={currency}
346
- sx={{ background: 'var(--tags-tag-green-bg, #B7FEE3)', color: 'var(--tags-tag-green-text, #007C52)' }}
347
391
  type="spent"
392
+ icon={<CreditCardOutlined color="warning" fontSize="small" />}
348
393
  />
349
394
  <CurrencyCard
350
395
  label={t('admin.customer.summary.stake')}
351
396
  data={data?.summary?.stake || emptyObject}
352
397
  currency={currency}
353
- sx={{ background: 'var(--tags-tag-blue-bg, #D2ECFF)', color: 'var(--tags-tag-blue-text, #0051E9)' }}
354
398
  type="stake"
399
+ icon={<AccountBalanceOutlined fontSize="small" color="info" />}
355
400
  />
356
401
  <CurrencyCard
357
402
  label={t('admin.customer.summary.refund')}
358
403
  data={data?.summary?.refund || emptyObject}
359
404
  currency={currency}
360
- sx={{ background: 'var(--tags-tag-purple-bg, #EFE9FF)', color: 'var(--tags-tag-purple-text, #007C52)' }}
361
405
  type="refund"
406
+ icon={
407
+ <AssignmentReturnOutlined fontSize="small" sx={{ color: 'var(--tags-tag-purple-icon, #7c3aed)' }} />
408
+ }
362
409
  />
363
410
  <CurrencyCard
364
411
  label={t('admin.customer.summary.due')}
365
412
  data={data?.summary?.due || emptyObject}
366
413
  currency={currency}
367
- sx={{ background: 'var(--tags-tag-red-bg, #FFE2E6)', color: 'var(--tags-tag-red-text, #E40031)' }}
368
414
  type="due"
415
+ icon={<InfoOutlined fontSize="small" color="error" />}
369
416
  />
370
417
  </Stack>
371
418
  </>
@@ -396,7 +443,7 @@ export default function CustomerHome() {
396
443
  )}
397
444
  </Box>
398
445
  <Box className="section-body">
399
- <CustomerInvoiceList customer_id={data?.id} type="table" include_staking />
446
+ <CustomerInvoiceList customer_id={data?.id} type="table" include_staking relatedSubscription />
400
447
  </Box>
401
448
  </Box>
402
449
  );
@@ -153,6 +153,7 @@ export default function CustomerInvoicePastDue() {
153
153
  action="pay"
154
154
  type="table"
155
155
  onTableDataChange={onTableDataChange}
156
+ relatedSubscription
156
157
  />
157
158
  </Box>
158
159
  </Box>
@@ -59,6 +59,8 @@ export default function PayoutDetail() {
59
59
  const currency = data.paymentCurrency;
60
60
  const total = [formatBNStr(data?.amount, currency.decimal), currency.symbol].join(' ');
61
61
  const { paymentIntent, paymentLink } = data || {};
62
+
63
+ const isAnonymousPayer = paymentIntent?.customer?.metadata?.anonymous;
62
64
  return (
63
65
  <Root direction="column" spacing={2.5} mb={4}>
64
66
  <Box>
@@ -160,23 +162,32 @@ export default function PayoutDetail() {
160
162
  : '',
161
163
  48
162
164
  )}
165
+ logoName={paymentIntent?.customer?.metadata?.anonymous ? '' : paymentIntent?.customer?.name}
163
166
  name={
164
167
  <Typography
165
168
  variant="subtitle2"
166
169
  sx={{
167
- cursor: 'pointer',
168
- '&:hover': {
169
- color: 'text.link',
170
- },
170
+ ...(isAnonymousPayer
171
+ ? {}
172
+ : {
173
+ cursor: 'pointer',
174
+ '&:hover': {
175
+ color: 'text.link',
176
+ },
177
+ }),
171
178
  }}
172
179
  onClick={() => {
180
+ if (isAnonymousPayer) {
181
+ return;
182
+ }
173
183
  const url = getCustomerProfileUrl({
174
184
  userDid: paymentIntent?.customer?.did,
175
185
  locale: 'zh',
176
186
  });
177
187
  window.open(url, '_blank');
178
188
  }}>
179
- {paymentIntent?.customer?.name} ({paymentIntent?.customer?.email})
189
+ {paymentIntent?.customer?.name}
190
+ {paymentIntent?.customer?.email ? ` (${paymentIntent?.customer?.email})` : ''}
180
191
  </Typography>
181
192
  }
182
193
  description={