@blocklet/payment-react 1.20.13 → 1.20.14
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/es/components/source-data-viewer.d.ts +23 -0
- package/es/components/source-data-viewer.js +228 -0
- package/es/history/credit/transactions-list.d.ts +1 -0
- package/es/history/credit/transactions-list.js +137 -20
- package/es/index.d.ts +2 -1
- package/es/index.js +3 -1
- package/es/locales/en.js +2 -1
- package/es/locales/zh.js +2 -1
- package/es/payment/index.js +31 -4
- package/es/payment/summary.js +6 -6
- package/lib/components/source-data-viewer.d.ts +23 -0
- package/lib/components/source-data-viewer.js +236 -0
- package/lib/history/credit/transactions-list.d.ts +1 -0
- package/lib/history/credit/transactions-list.js +132 -16
- package/lib/index.d.ts +2 -1
- package/lib/index.js +8 -0
- package/lib/locales/en.js +2 -1
- package/lib/locales/zh.js +2 -1
- package/lib/payment/index.js +31 -4
- package/lib/payment/summary.js +6 -6
- package/package.json +3 -3
- package/src/components/source-data-viewer.tsx +295 -0
- package/src/history/credit/transactions-list.tsx +142 -18
- package/src/index.ts +2 -0
- package/src/locales/en.tsx +1 -0
- package/src/locales/zh.tsx +1 -0
- package/src/payment/index.tsx +32 -4
- package/src/payment/summary.tsx +7 -7
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
/* eslint-disable react/no-unstable-nested-components */
|
|
7
7
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
8
8
|
import type { Paginated, TCreditTransactionExpanded } from '@blocklet/payment-types';
|
|
9
|
-
import { Box, Typography, Stack, Link,
|
|
9
|
+
import { Box, Typography, Grid, Stack, Link, Button, Popover } from '@mui/material';
|
|
10
10
|
import { useRequest } from 'ahooks';
|
|
11
11
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
12
12
|
import { useNavigate } from 'react-router-dom';
|
|
13
13
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
14
|
-
import { joinURL } from 'ufo';
|
|
15
14
|
import { styled } from '@mui/system';
|
|
15
|
+
import { joinURL } from 'ufo';
|
|
16
16
|
import DateRangePicker, { type DateRangeValue } from '../../components/date-range-picker';
|
|
17
17
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
18
18
|
|
|
@@ -21,6 +21,7 @@ import { usePaymentContext } from '../../contexts/payment';
|
|
|
21
21
|
import api from '../../libs/api';
|
|
22
22
|
import Table from '../../components/table';
|
|
23
23
|
import { createLink, handleNavigation } from '../../libs/navigation';
|
|
24
|
+
import SourceDataViewer from '../../components/source-data-viewer';
|
|
24
25
|
|
|
25
26
|
type Result = Paginated<TCreditTransactionExpanded>;
|
|
26
27
|
|
|
@@ -43,6 +44,7 @@ type Props = {
|
|
|
43
44
|
onTableDataChange?: Function;
|
|
44
45
|
showAdminColumns?: boolean;
|
|
45
46
|
showTimeFilter?: boolean;
|
|
47
|
+
includeGrants?: boolean; // Enable unified cash flow view
|
|
46
48
|
source?: string;
|
|
47
49
|
mode?: 'dashboard' | 'portal';
|
|
48
50
|
};
|
|
@@ -59,6 +61,18 @@ const getGrantDetailLink = (grantId: string, inDashboard: boolean) => {
|
|
|
59
61
|
};
|
|
60
62
|
};
|
|
61
63
|
|
|
64
|
+
const getInvoiceDetailLink = (invoiceId: string, inDashboard: boolean) => {
|
|
65
|
+
let path = `/customer/invoice/${invoiceId}`;
|
|
66
|
+
if (inDashboard) {
|
|
67
|
+
path = `/admin/billing/${invoiceId}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
link: createLink(path),
|
|
72
|
+
connect: false,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
62
76
|
const TransactionsTable = React.memo((props: Props) => {
|
|
63
77
|
const {
|
|
64
78
|
pageSize,
|
|
@@ -68,6 +82,7 @@ const TransactionsTable = React.memo((props: Props) => {
|
|
|
68
82
|
onTableDataChange,
|
|
69
83
|
showAdminColumns = false,
|
|
70
84
|
showTimeFilter = false,
|
|
85
|
+
includeGrants = false,
|
|
71
86
|
source,
|
|
72
87
|
mode = 'portal',
|
|
73
88
|
} = props;
|
|
@@ -95,6 +110,12 @@ const TransactionsTable = React.memo((props: Props) => {
|
|
|
95
110
|
end: undefined,
|
|
96
111
|
});
|
|
97
112
|
|
|
113
|
+
// Source Data Popover state
|
|
114
|
+
const [sourceDataPopover, setSourceDataPopover] = useState<{
|
|
115
|
+
anchorEl: HTMLElement | null;
|
|
116
|
+
data: any;
|
|
117
|
+
}>({ anchorEl: null, data: null });
|
|
118
|
+
|
|
98
119
|
const handleDateRangeChange = useCallback((newValue: DateRangeValue) => {
|
|
99
120
|
setFilters(newValue);
|
|
100
121
|
setSearch((prev) => ({
|
|
@@ -113,9 +134,10 @@ const TransactionsTable = React.memo((props: Props) => {
|
|
|
113
134
|
subscription_id,
|
|
114
135
|
credit_grant_id,
|
|
115
136
|
source,
|
|
137
|
+
include_grants: includeGrants,
|
|
116
138
|
}),
|
|
117
139
|
{
|
|
118
|
-
refreshDeps: [search, effectiveCustomerId, subscription_id, credit_grant_id, source],
|
|
140
|
+
refreshDeps: [search, effectiveCustomerId, subscription_id, credit_grant_id, source, includeGrants],
|
|
119
141
|
}
|
|
120
142
|
);
|
|
121
143
|
|
|
@@ -143,35 +165,55 @@ const TransactionsTable = React.memo((props: Props) => {
|
|
|
143
165
|
align: 'right',
|
|
144
166
|
options: {
|
|
145
167
|
customBodyRenderLite: (_: string, index: number) => {
|
|
146
|
-
const
|
|
147
|
-
const
|
|
168
|
+
const item = data?.list[index] as any;
|
|
169
|
+
const isGrant = item.activity_type === 'grant';
|
|
170
|
+
const amount = isGrant ? item.amount : item.credit_amount;
|
|
171
|
+
const currency = item.paymentCurrency || item.currency;
|
|
172
|
+
const unit = !isGrant && item.meter?.unit ? item.meter.unit : currency?.symbol;
|
|
173
|
+
const displayAmount = formatBNStr(amount, currency?.decimal || 0);
|
|
174
|
+
|
|
175
|
+
if (!includeGrants) {
|
|
176
|
+
return (
|
|
177
|
+
<Typography>
|
|
178
|
+
{displayAmount} {unit}
|
|
179
|
+
</Typography>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
148
183
|
return (
|
|
149
|
-
<Typography
|
|
150
|
-
{
|
|
184
|
+
<Typography
|
|
185
|
+
sx={{
|
|
186
|
+
color: isGrant ? 'success.main' : 'error.main',
|
|
187
|
+
}}>
|
|
188
|
+
{isGrant ? '+' : '-'} {displayAmount} {unit}
|
|
151
189
|
</Typography>
|
|
152
190
|
);
|
|
153
191
|
},
|
|
154
192
|
},
|
|
155
193
|
},
|
|
156
|
-
|
|
194
|
+
{
|
|
157
195
|
label: t('common.creditGrant'),
|
|
158
196
|
name: 'credit_grant',
|
|
159
197
|
options: {
|
|
160
198
|
customBodyRenderLite: (_: string, index: number) => {
|
|
161
|
-
const
|
|
199
|
+
const item = data?.list[index] as any;
|
|
200
|
+
const isGrant = item.activity_type === 'grant';
|
|
201
|
+
|
|
202
|
+
const grantName = isGrant ? item.name : item.creditGrant.name;
|
|
203
|
+
const grantId = isGrant ? item.id : item.credit_grant_id;
|
|
162
204
|
return (
|
|
163
205
|
<Stack
|
|
164
206
|
direction="row"
|
|
165
207
|
spacing={1}
|
|
166
208
|
onClick={(e) => {
|
|
167
|
-
const link = getGrantDetailLink(
|
|
209
|
+
const link = getGrantDetailLink(grantId, isAdmin && mode === 'dashboard');
|
|
168
210
|
handleNavigation(e, link.link, navigate);
|
|
169
211
|
}}
|
|
170
212
|
sx={{
|
|
171
213
|
alignItems: 'center',
|
|
172
214
|
}}>
|
|
173
|
-
<Typography variant="body2" sx={{
|
|
174
|
-
{
|
|
215
|
+
<Typography variant="body2" sx={{ cursor: 'pointer' }}>
|
|
216
|
+
{grantName || `Grant ${grantId.slice(-6)}`}
|
|
175
217
|
</Typography>
|
|
176
218
|
</Stack>
|
|
177
219
|
);
|
|
@@ -180,12 +222,19 @@ const TransactionsTable = React.memo((props: Props) => {
|
|
|
180
222
|
},
|
|
181
223
|
{
|
|
182
224
|
label: t('common.description'),
|
|
183
|
-
name: '
|
|
225
|
+
name: 'description',
|
|
184
226
|
options: {
|
|
185
227
|
customBodyRenderLite: (_: string, index: number) => {
|
|
186
|
-
const
|
|
228
|
+
const item = data?.list[index] as any;
|
|
229
|
+
const isGrant = item.activity_type === 'grant';
|
|
230
|
+
const description = isGrant
|
|
231
|
+
? item.name || item.description || 'Credit Granted'
|
|
232
|
+
: item.subscription?.description || item.description || `${item.meter_event_name} usage`;
|
|
233
|
+
|
|
187
234
|
return (
|
|
188
|
-
<Typography variant="body2"
|
|
235
|
+
<Typography variant="body2" sx={{ fontWeight: 400 }}>
|
|
236
|
+
{description}
|
|
237
|
+
</Typography>
|
|
189
238
|
);
|
|
190
239
|
},
|
|
191
240
|
},
|
|
@@ -218,15 +267,60 @@ const TransactionsTable = React.memo((props: Props) => {
|
|
|
218
267
|
name: 'created_at',
|
|
219
268
|
options: {
|
|
220
269
|
customBodyRenderLite: (_: string, index: number) => {
|
|
221
|
-
const
|
|
270
|
+
const item = data?.list[index] as any;
|
|
222
271
|
return (
|
|
223
|
-
<Typography variant="body2">
|
|
224
|
-
{formatToDate(
|
|
272
|
+
<Typography variant="body2" color="text.secondary" sx={{ fontSize: '0.875rem' }}>
|
|
273
|
+
{formatToDate(item.created_at, locale, 'YYYY-MM-DD HH:mm')}
|
|
225
274
|
</Typography>
|
|
226
275
|
);
|
|
227
276
|
},
|
|
228
277
|
},
|
|
229
278
|
},
|
|
279
|
+
{
|
|
280
|
+
label: t('common.actions'),
|
|
281
|
+
name: 'actions',
|
|
282
|
+
options: {
|
|
283
|
+
customBodyRenderLite: (_: string, index: number) => {
|
|
284
|
+
const item = data?.list[index] as any;
|
|
285
|
+
const isGrant = item.activity_type === 'grant';
|
|
286
|
+
const invoiceId = isGrant ? item.metadata?.invoice_id : null;
|
|
287
|
+
const sourceData = !isGrant && item.meterEvent?.source_data;
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
|
|
291
|
+
{isGrant && invoiceId && (
|
|
292
|
+
<Button
|
|
293
|
+
variant="text"
|
|
294
|
+
size="small"
|
|
295
|
+
color="primary"
|
|
296
|
+
onClick={(e) => {
|
|
297
|
+
e.preventDefault();
|
|
298
|
+
const link = getInvoiceDetailLink(invoiceId, isAdmin && mode === 'dashboard');
|
|
299
|
+
handleNavigation(e, link.link, navigate);
|
|
300
|
+
}}>
|
|
301
|
+
{t('common.viewInvoice')}
|
|
302
|
+
</Button>
|
|
303
|
+
)}
|
|
304
|
+
{sourceData && (
|
|
305
|
+
<Button
|
|
306
|
+
variant="text"
|
|
307
|
+
size="small"
|
|
308
|
+
color="primary"
|
|
309
|
+
onClick={(e) => {
|
|
310
|
+
e.preventDefault();
|
|
311
|
+
setSourceDataPopover({
|
|
312
|
+
anchorEl: e.currentTarget,
|
|
313
|
+
data: sourceData,
|
|
314
|
+
});
|
|
315
|
+
}}>
|
|
316
|
+
{t('common.viewSourceData')}
|
|
317
|
+
</Button>
|
|
318
|
+
)}
|
|
319
|
+
</Box>
|
|
320
|
+
);
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
230
324
|
].filter(Boolean);
|
|
231
325
|
|
|
232
326
|
const onTableChange = ({ page, rowsPerPage }: any) => {
|
|
@@ -279,6 +373,35 @@ const TransactionsTable = React.memo((props: Props) => {
|
|
|
279
373
|
mobileTDFlexDirection="row"
|
|
280
374
|
emptyNodeText={t('admin.creditTransactions.noTransactions')}
|
|
281
375
|
/>
|
|
376
|
+
<Popover
|
|
377
|
+
open={Boolean(sourceDataPopover.anchorEl)}
|
|
378
|
+
anchorEl={sourceDataPopover.anchorEl}
|
|
379
|
+
onClose={() => setSourceDataPopover({ anchorEl: null, data: null })}
|
|
380
|
+
anchorOrigin={{
|
|
381
|
+
vertical: 'bottom',
|
|
382
|
+
horizontal: 'left',
|
|
383
|
+
}}
|
|
384
|
+
transformOrigin={{
|
|
385
|
+
vertical: 'top',
|
|
386
|
+
horizontal: 'left',
|
|
387
|
+
}}
|
|
388
|
+
slotProps={{
|
|
389
|
+
paper: {
|
|
390
|
+
sx: {
|
|
391
|
+
minWidth: {
|
|
392
|
+
xs: 0,
|
|
393
|
+
md: 320,
|
|
394
|
+
},
|
|
395
|
+
maxHeight: 450,
|
|
396
|
+
p: {
|
|
397
|
+
xs: 1,
|
|
398
|
+
md: 3,
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
}}>
|
|
403
|
+
{sourceDataPopover.data && <SourceDataViewer data={sourceDataPopover.data} showGroups />}
|
|
404
|
+
</Popover>
|
|
282
405
|
</TableRoot>
|
|
283
406
|
);
|
|
284
407
|
});
|
|
@@ -308,6 +431,7 @@ export default function CreditTransactionsList(rawProps: Props) {
|
|
|
308
431
|
onTableDataChange: () => {},
|
|
309
432
|
showAdminColumns: false,
|
|
310
433
|
showTimeFilter: false,
|
|
434
|
+
includeGrants: false,
|
|
311
435
|
mode: 'portal',
|
|
312
436
|
},
|
|
313
437
|
rawProps
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,7 @@ import AutoTopupModal from './components/auto-topup/modal';
|
|
|
40
40
|
import AutoTopup from './components/auto-topup';
|
|
41
41
|
import Collapse from './components/collapse';
|
|
42
42
|
import PromotionCode from './components/promotion-code';
|
|
43
|
+
import SourceDataViewer from './components/source-data-viewer';
|
|
43
44
|
|
|
44
45
|
export { PaymentThemeProvider } from './theme';
|
|
45
46
|
|
|
@@ -104,4 +105,5 @@ export {
|
|
|
104
105
|
AutoTopup,
|
|
105
106
|
Collapse,
|
|
106
107
|
PromotionCode,
|
|
108
|
+
SourceDataViewer,
|
|
107
109
|
};
|
package/src/locales/en.tsx
CHANGED
package/src/locales/zh.tsx
CHANGED
package/src/payment/index.tsx
CHANGED
|
@@ -173,10 +173,7 @@ function PaymentInner({
|
|
|
173
173
|
settings.baseCurrency;
|
|
174
174
|
const method = paymentMethods.find((x: any) => x.id === currency.payment_method_id) as TPaymentMethod;
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
if (onChange) {
|
|
178
|
-
onChange(methods.getValues());
|
|
179
|
-
}
|
|
176
|
+
const recalculatePromotion = () => {
|
|
180
177
|
if ((state.checkoutSession as any)?.discounts?.length) {
|
|
181
178
|
api
|
|
182
179
|
.post(`/api/checkout-sessions/${state.checkoutSession.id}/recalculate-promotion`, {
|
|
@@ -186,11 +183,22 @@ function PaymentInner({
|
|
|
186
183
|
onPromotionUpdate();
|
|
187
184
|
});
|
|
188
185
|
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
if (onChange) {
|
|
190
|
+
onChange(methods.getValues());
|
|
191
|
+
}
|
|
192
|
+
recalculatePromotion();
|
|
189
193
|
}, [currencyId]); // eslint-disable-line
|
|
190
194
|
|
|
191
195
|
const onUpsell = async (from: string, to: string) => {
|
|
192
196
|
try {
|
|
193
197
|
const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/upsell`, { from, to });
|
|
198
|
+
if (data.discounts?.length) {
|
|
199
|
+
recalculatePromotion();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
194
202
|
setState({ checkoutSession: data });
|
|
195
203
|
} catch (err) {
|
|
196
204
|
console.error(err);
|
|
@@ -201,6 +209,10 @@ function PaymentInner({
|
|
|
201
209
|
const onDownsell = async (from: string) => {
|
|
202
210
|
try {
|
|
203
211
|
const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/downsell`, { from });
|
|
212
|
+
if (data.discounts?.length) {
|
|
213
|
+
recalculatePromotion();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
204
216
|
setState({ checkoutSession: data });
|
|
205
217
|
} catch (err) {
|
|
206
218
|
console.error(err);
|
|
@@ -211,6 +223,10 @@ function PaymentInner({
|
|
|
211
223
|
const onApplyCrossSell = async (to: string) => {
|
|
212
224
|
try {
|
|
213
225
|
const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/cross-sell`, { to });
|
|
226
|
+
if (data.discounts?.length) {
|
|
227
|
+
recalculatePromotion();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
214
230
|
setState({ checkoutSession: data });
|
|
215
231
|
} catch (err) {
|
|
216
232
|
console.error(err);
|
|
@@ -224,6 +240,10 @@ function PaymentInner({
|
|
|
224
240
|
itemId,
|
|
225
241
|
quantity,
|
|
226
242
|
});
|
|
243
|
+
if (data.discounts?.length) {
|
|
244
|
+
recalculatePromotion();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
227
247
|
setState({ checkoutSession: data });
|
|
228
248
|
} catch (err) {
|
|
229
249
|
console.error(err);
|
|
@@ -234,6 +254,10 @@ function PaymentInner({
|
|
|
234
254
|
const onCancelCrossSell = async () => {
|
|
235
255
|
try {
|
|
236
256
|
const { data } = await api.delete(`/api/checkout-sessions/${state.checkoutSession.id}/cross-sell`);
|
|
257
|
+
if (data.discounts?.length) {
|
|
258
|
+
recalculatePromotion();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
237
261
|
setState({ checkoutSession: data });
|
|
238
262
|
} catch (err) {
|
|
239
263
|
console.error(err);
|
|
@@ -247,6 +271,10 @@ function PaymentInner({
|
|
|
247
271
|
priceId,
|
|
248
272
|
amount: fromTokenToUnit(amount, currency.decimal).toString(),
|
|
249
273
|
});
|
|
274
|
+
if (data.discounts?.length) {
|
|
275
|
+
recalculatePromotion();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
250
278
|
setState({ checkoutSession: data });
|
|
251
279
|
} catch (err) {
|
|
252
280
|
console.error(err);
|
package/src/payment/summary.tsx
CHANGED
|
@@ -92,9 +92,9 @@ type Props = {
|
|
|
92
92
|
showFeatures?: boolean;
|
|
93
93
|
};
|
|
94
94
|
|
|
95
|
-
async function fetchCrossSell(id: string) {
|
|
95
|
+
async function fetchCrossSell(id: string, skipError = true) {
|
|
96
96
|
try {
|
|
97
|
-
const { data } = await api.get(`/api/checkout-sessions/${id}/cross-sell`);
|
|
97
|
+
const { data } = await api.get(`/api/checkout-sessions/${id}/cross-sell?skipError=${skipError}`);
|
|
98
98
|
if (!data.error) {
|
|
99
99
|
return data;
|
|
100
100
|
}
|
|
@@ -167,8 +167,8 @@ export default function PaymentSummary({
|
|
|
167
167
|
const { isMobile } = useMobile();
|
|
168
168
|
const { paymentState, ...settings } = usePaymentContext();
|
|
169
169
|
const [state, setState] = useSetState({ loading: false, shake: false, expanded: items?.length < 3 });
|
|
170
|
-
const { data, runAsync } = useRequest(() =>
|
|
171
|
-
checkoutSessionId ? fetchCrossSell(checkoutSessionId) : Promise.resolve(null)
|
|
170
|
+
const { data, runAsync } = useRequest((skipError) =>
|
|
171
|
+
checkoutSessionId ? fetchCrossSell(checkoutSessionId, skipError) : Promise.resolve(null)
|
|
172
172
|
);
|
|
173
173
|
|
|
174
174
|
const sessionDiscounts = (checkoutSession as any)?.discounts || [];
|
|
@@ -238,17 +238,17 @@ export default function PaymentSummary({
|
|
|
238
238
|
|
|
239
239
|
const handleUpsell = async (from: string, to: string) => {
|
|
240
240
|
await onUpsell!(from, to);
|
|
241
|
-
runAsync();
|
|
241
|
+
runAsync(false);
|
|
242
242
|
};
|
|
243
243
|
|
|
244
244
|
const handleQuantityChange = async (itemId: string, quantity: number) => {
|
|
245
245
|
await onQuantityChange!(itemId, quantity);
|
|
246
|
-
runAsync();
|
|
246
|
+
runAsync(false);
|
|
247
247
|
};
|
|
248
248
|
|
|
249
249
|
const handleDownsell = async (from: string) => {
|
|
250
250
|
await onDownsell!(from);
|
|
251
|
-
runAsync();
|
|
251
|
+
runAsync(false);
|
|
252
252
|
};
|
|
253
253
|
|
|
254
254
|
const handleApplyCrossSell = async () => {
|