payment-kit 1.15.2 → 1.15.4
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/api/src/crons/payment-stat.ts +1 -0
- package/api/src/index.ts +2 -2
- package/api/src/integrations/arcblock/stake.ts +17 -10
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +15 -8
- package/api/src/libs/notification/template/one-time-payment-succeeded.ts +1 -30
- package/api/src/libs/notification/template/subscription-canceled.ts +45 -23
- package/api/src/libs/notification/template/subscription-refund-succeeded.ts +130 -47
- package/api/src/libs/notification/template/subscription-renewed.ts +10 -2
- package/api/src/libs/notification/template/subscription-stake-slash-succeeded.ts +228 -0
- package/api/src/libs/notification/template/subscription-succeeded.ts +2 -2
- package/api/src/libs/notification/template/subscription-trial-start.ts +7 -10
- package/api/src/libs/notification/template/subscription-trial-will-end.ts +13 -5
- package/api/src/libs/notification/template/subscription-will-renew.ts +41 -29
- package/api/src/libs/payment.ts +53 -1
- package/api/src/libs/subscription.ts +43 -0
- package/api/src/locales/en.ts +24 -0
- package/api/src/locales/zh.ts +22 -0
- package/api/src/queues/invoice.ts +1 -1
- package/api/src/queues/notification.ts +9 -0
- package/api/src/queues/payment.ts +17 -0
- package/api/src/routes/checkout-sessions.ts +13 -1
- package/api/src/routes/payment-stats.ts +3 -3
- package/api/src/routes/subscriptions.ts +26 -6
- package/api/src/store/migrations/20240905-index.ts +100 -0
- package/api/src/store/models/subscription.ts +1 -0
- package/api/tests/libs/payment.spec.ts +168 -0
- package/blocklet.yml +1 -1
- package/package.json +9 -9
- package/src/components/balance-list.tsx +2 -2
- package/src/components/invoice/list.tsx +2 -2
- package/src/components/invoice/table.tsx +1 -1
- package/src/components/payment-intent/list.tsx +1 -1
- package/src/components/payouts/list.tsx +1 -1
- package/src/components/refund/list.tsx +2 -2
- package/src/components/subscription/actions/cancel.tsx +41 -13
- package/src/components/subscription/actions/index.tsx +11 -8
- package/src/components/subscription/actions/slash-stake.tsx +52 -0
- package/src/locales/en.tsx +1 -0
- package/src/locales/zh.tsx +1 -0
- package/src/pages/admin/billing/invoices/detail.tsx +2 -2
- package/src/pages/customer/refund/list.tsx +1 -1
- package/src/pages/customer/subscription/detail.tsx +1 -1
|
@@ -36,7 +36,8 @@ export default function SubscriptionCancelForm({ data }: { data: TSubscriptionEx
|
|
|
36
36
|
setValue('cancel.refund', 'none');
|
|
37
37
|
}
|
|
38
38
|
}, [cancelAt, cancelTime]); // eslint-disable-line
|
|
39
|
-
|
|
39
|
+
const stakingDisabled = !staking || staking?.return_amount === '0';
|
|
40
|
+
const refundDisabled = !refund || refund.total === '0';
|
|
40
41
|
const isCustom = cancelAt === 'custom';
|
|
41
42
|
const { decimal, symbol } = data.paymentCurrency;
|
|
42
43
|
|
|
@@ -95,15 +96,15 @@ export default function SubscriptionCancelForm({ data }: { data: TSubscriptionEx
|
|
|
95
96
|
<RadioGroup>
|
|
96
97
|
<FormControlLabel
|
|
97
98
|
value="none"
|
|
98
|
-
disabled={loading ||
|
|
99
|
-
onClick={() => !(loading ||
|
|
99
|
+
disabled={loading || refundDisabled}
|
|
100
|
+
onClick={() => !(loading || refundDisabled) && setValue('cancel.refund', 'none')}
|
|
100
101
|
control={<Radio checked={refundType === 'none'} />}
|
|
101
102
|
label={t('admin.subscription.cancel.refund.none')}
|
|
102
103
|
/>
|
|
103
104
|
<FormControlLabel
|
|
104
105
|
value="last"
|
|
105
|
-
disabled={loading ||
|
|
106
|
-
onClick={() => !(loading ||
|
|
106
|
+
disabled={loading || refundDisabled}
|
|
107
|
+
onClick={() => !(loading || refundDisabled) && setValue('cancel.refund', 'last')}
|
|
107
108
|
control={<Radio checked={refundType === 'last'} />}
|
|
108
109
|
label={t('admin.subscription.cancel.refund.last', {
|
|
109
110
|
symbol,
|
|
@@ -112,8 +113,8 @@ export default function SubscriptionCancelForm({ data }: { data: TSubscriptionEx
|
|
|
112
113
|
/>
|
|
113
114
|
<FormControlLabel
|
|
114
115
|
value="proration"
|
|
115
|
-
disabled={loading ||
|
|
116
|
-
onClick={() => !(loading ||
|
|
116
|
+
disabled={loading || refundDisabled}
|
|
117
|
+
onClick={() => !(loading || refundDisabled) && setValue('cancel.refund', 'proration')}
|
|
117
118
|
control={<Radio checked={refundType === 'proration'} />}
|
|
118
119
|
label={t('admin.subscription.cancel.refund.proration', {
|
|
119
120
|
symbol,
|
|
@@ -132,15 +133,15 @@ export default function SubscriptionCancelForm({ data }: { data: TSubscriptionEx
|
|
|
132
133
|
<RadioGroup>
|
|
133
134
|
<FormControlLabel
|
|
134
135
|
value="none"
|
|
135
|
-
disabled={loading ||
|
|
136
|
-
onClick={() => !(loading ||
|
|
136
|
+
disabled={loading || stakingDisabled}
|
|
137
|
+
onClick={() => !(loading || stakingDisabled) && setValue('cancel.staking', 'none')}
|
|
137
138
|
control={<Radio checked={stakingType === 'none'} />}
|
|
138
139
|
label={t('admin.subscription.cancel.staking.none')}
|
|
139
140
|
/>
|
|
140
141
|
<FormControlLabel
|
|
141
142
|
value="proration"
|
|
142
|
-
disabled={loading ||
|
|
143
|
-
onClick={() => !(loading ||
|
|
143
|
+
disabled={loading || stakingDisabled}
|
|
144
|
+
onClick={() => !(loading || stakingDisabled) && setValue('cancel.staking', 'proration')}
|
|
144
145
|
control={<Radio checked={stakingType === 'proration'} />}
|
|
145
146
|
label={t('admin.subscription.cancel.staking.proration', {
|
|
146
147
|
unused: formatAmount(staking?.return_amount || '0', decimal),
|
|
@@ -149,8 +150,8 @@ export default function SubscriptionCancelForm({ data }: { data: TSubscriptionEx
|
|
|
149
150
|
/>
|
|
150
151
|
<FormControlLabel
|
|
151
152
|
value="slash"
|
|
152
|
-
disabled={loading ||
|
|
153
|
-
onClick={() => !(loading ||
|
|
153
|
+
disabled={loading || stakingDisabled}
|
|
154
|
+
onClick={() => !(loading || stakingDisabled) && setValue('cancel.staking', 'slash')}
|
|
154
155
|
control={<Radio checked={stakingType === 'slash'} />}
|
|
155
156
|
label={t('admin.subscription.cancel.staking.slash', {
|
|
156
157
|
unused: formatAmount(staking?.slash_amount || '0', decimal),
|
|
@@ -159,6 +160,33 @@ export default function SubscriptionCancelForm({ data }: { data: TSubscriptionEx
|
|
|
159
160
|
/>
|
|
160
161
|
</RadioGroup>
|
|
161
162
|
</Stack>
|
|
163
|
+
{stakingType === 'slash' && (
|
|
164
|
+
<Controller
|
|
165
|
+
name="cancel.slashReason"
|
|
166
|
+
control={control}
|
|
167
|
+
rules={{
|
|
168
|
+
required: t('common.required'),
|
|
169
|
+
validate: (value) => value.trim() !== '' || t('common.required'),
|
|
170
|
+
}}
|
|
171
|
+
render={({ field }) => (
|
|
172
|
+
<TextField
|
|
173
|
+
{...field}
|
|
174
|
+
variant="outlined"
|
|
175
|
+
size="small"
|
|
176
|
+
fullWidth
|
|
177
|
+
multiline
|
|
178
|
+
minRows={2}
|
|
179
|
+
maxRows={4}
|
|
180
|
+
placeholder={t('admin.subscription.cancel.staking.slashReason')}
|
|
181
|
+
error={!!(formState.errors as any)?.cancel?.slashReason}
|
|
182
|
+
helperText={(formState.errors as any)?.cancel?.slashReason?.message}
|
|
183
|
+
inputProps={{
|
|
184
|
+
maxLength: 200,
|
|
185
|
+
}}
|
|
186
|
+
/>
|
|
187
|
+
)}
|
|
188
|
+
/>
|
|
189
|
+
)}
|
|
162
190
|
</>
|
|
163
191
|
)}
|
|
164
192
|
</Root>
|
|
@@ -12,6 +12,7 @@ import Actions from '../../actions';
|
|
|
12
12
|
import ClickBoundary from '../../click-boundary';
|
|
13
13
|
import SubscriptionCancelForm from './cancel';
|
|
14
14
|
import SubscriptionPauseForm from './pause';
|
|
15
|
+
import SlashStakeForm from './slash-stake';
|
|
15
16
|
|
|
16
17
|
type Props = {
|
|
17
18
|
data: TSubscriptionExpanded;
|
|
@@ -30,7 +31,7 @@ const fetchStakingData = (id: string, time: string): Promise<{ return_amount: st
|
|
|
30
31
|
function SubscriptionActionsInner({ data, variant, onChange }: Props) {
|
|
31
32
|
const { t } = useLocaleContext();
|
|
32
33
|
const navigate = useNavigate();
|
|
33
|
-
const { reset, getValues, setError } = useFormContext();
|
|
34
|
+
const { reset, getValues, setError, handleSubmit } = useFormContext();
|
|
34
35
|
const [state, setState] = useSetState({
|
|
35
36
|
action: '',
|
|
36
37
|
loading: false,
|
|
@@ -69,7 +70,8 @@ function SubscriptionActionsInner({ data, variant, onChange }: Props) {
|
|
|
69
70
|
|
|
70
71
|
try {
|
|
71
72
|
setState({ loading: true });
|
|
72
|
-
|
|
73
|
+
const key = action === 'slash-stake' ? 'slashStake' : action;
|
|
74
|
+
await api.put(`/api/subscriptions/${data.id}/${action}`, values[key] || {}).then((res) => res.data);
|
|
73
75
|
Toast.success(t('common.saved'));
|
|
74
76
|
onChange(state.action);
|
|
75
77
|
} catch (err) {
|
|
@@ -148,7 +150,7 @@ function SubscriptionActionsInner({ data, variant, onChange }: Props) {
|
|
|
148
150
|
<Actions variant={variant} actions={actions} onOpenCallback={fetchStakeResultAsync} />
|
|
149
151
|
{state.action === 'cancel' && (
|
|
150
152
|
<ConfirmDialog
|
|
151
|
-
onConfirm={createHandler('cancel')}
|
|
153
|
+
onConfirm={handleSubmit(createHandler('cancel'))}
|
|
152
154
|
onCancel={handleCancel}
|
|
153
155
|
title={t('admin.subscription.cancel.title')}
|
|
154
156
|
message={<SubscriptionCancelForm data={data} />}
|
|
@@ -175,13 +177,10 @@ function SubscriptionActionsInner({ data, variant, onChange }: Props) {
|
|
|
175
177
|
)}
|
|
176
178
|
{state.action === 'slashStake' && (
|
|
177
179
|
<ConfirmDialog
|
|
178
|
-
onConfirm={createHandler('slash-stake')}
|
|
180
|
+
onConfirm={handleSubmit(createHandler('slash-stake'))}
|
|
179
181
|
onCancel={handleCancel}
|
|
180
182
|
title={t('admin.subscription.cancel.staking.slashTitle')}
|
|
181
|
-
message={
|
|
182
|
-
unused: stakeValue,
|
|
183
|
-
symbol: data.paymentCurrency?.symbol,
|
|
184
|
-
})}
|
|
183
|
+
message={<SlashStakeForm data={data} stakeValue={stakeValue} />}
|
|
185
184
|
loading={state.loading}
|
|
186
185
|
/>
|
|
187
186
|
)}
|
|
@@ -197,12 +196,16 @@ export default function SubscriptionActions(props: Props) {
|
|
|
197
196
|
time: '',
|
|
198
197
|
refund: 'none',
|
|
199
198
|
staking: 'none',
|
|
199
|
+
slashReason: '',
|
|
200
200
|
},
|
|
201
201
|
pause: {
|
|
202
202
|
type: 'never',
|
|
203
203
|
resumesAt: '',
|
|
204
204
|
behavior: 'void',
|
|
205
205
|
},
|
|
206
|
+
slashStake: {
|
|
207
|
+
slashReason: '',
|
|
208
|
+
},
|
|
206
209
|
},
|
|
207
210
|
});
|
|
208
211
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
+
import type { TSubscriptionExpanded } from '@blocklet/payment-types';
|
|
3
|
+
import { Box, TextField, styled } from '@mui/material';
|
|
4
|
+
import { Controller, useFormContext } from 'react-hook-form';
|
|
5
|
+
|
|
6
|
+
export default function SlashStakeForm({ data, stakeValue }: { data: TSubscriptionExpanded; stakeValue: string }) {
|
|
7
|
+
const { t } = useLocaleContext();
|
|
8
|
+
const { control, formState } = useFormContext();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Root sx={{ width: 400 }}>
|
|
12
|
+
{t('admin.subscription.cancel.staking.slashTip', {
|
|
13
|
+
unused: stakeValue,
|
|
14
|
+
symbol: data.paymentCurrency?.symbol,
|
|
15
|
+
})}
|
|
16
|
+
<Controller
|
|
17
|
+
name="slashStake.slashReason"
|
|
18
|
+
control={control}
|
|
19
|
+
rules={{
|
|
20
|
+
required: t('common.required'),
|
|
21
|
+
validate: (value) => {
|
|
22
|
+
return value.trim() !== '' || t('common.required');
|
|
23
|
+
},
|
|
24
|
+
}}
|
|
25
|
+
render={({ field }) => (
|
|
26
|
+
<TextField
|
|
27
|
+
{...field}
|
|
28
|
+
variant="outlined"
|
|
29
|
+
size="small"
|
|
30
|
+
fullWidth
|
|
31
|
+
multiline
|
|
32
|
+
minRows={2}
|
|
33
|
+
maxRows={4}
|
|
34
|
+
placeholder={t('admin.subscription.cancel.staking.slashReason')}
|
|
35
|
+
error={!!(formState.errors as any)?.slashStake?.slashReason}
|
|
36
|
+
helperText={(formState.errors as any)?.slashStake?.slashReason?.message}
|
|
37
|
+
inputProps={{
|
|
38
|
+
maxLength: 200,
|
|
39
|
+
}}
|
|
40
|
+
sx={{ mt: 1 }}
|
|
41
|
+
/>
|
|
42
|
+
)}
|
|
43
|
+
/>
|
|
44
|
+
</Root>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const Root = styled(Box)`
|
|
49
|
+
.form-title {
|
|
50
|
+
width: 60px;
|
|
51
|
+
}
|
|
52
|
+
`;
|
package/src/locales/en.tsx
CHANGED
|
@@ -469,6 +469,7 @@ export default flat({
|
|
|
469
469
|
none: 'No return or slash',
|
|
470
470
|
proration: 'Return Remaining Stake {unused}{symbol}',
|
|
471
471
|
slash: 'Slash Remaining Stake {unused}{symbol}',
|
|
472
|
+
slashReason: 'Slash Reason',
|
|
472
473
|
slashTip:
|
|
473
474
|
'The remaining stake of this subscription {unused}{symbol} will be slashed, please confirm to continue?',
|
|
474
475
|
slashTitle: 'Slash stake',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -289,7 +289,7 @@ export default function InvoiceDetail(props: { id: string }) {
|
|
|
289
289
|
/>
|
|
290
290
|
<InfoRow
|
|
291
291
|
label={t('admin.paymentMethod._name')}
|
|
292
|
-
value={<Currency logo={data.paymentMethod
|
|
292
|
+
value={<Currency logo={data.paymentMethod?.logo} name={data.paymentMethod?.name} />}
|
|
293
293
|
direction={InfoDirection}
|
|
294
294
|
alignItems={InfoAlignItems}
|
|
295
295
|
/>
|
|
@@ -298,7 +298,7 @@ export default function InvoiceDetail(props: { id: string }) {
|
|
|
298
298
|
value={
|
|
299
299
|
<Currency
|
|
300
300
|
logo={data.paymentCurrency.logo}
|
|
301
|
-
name={`${data.paymentCurrency.symbol} (${data.paymentMethod
|
|
301
|
+
name={`${data.paymentCurrency.symbol} (${data.paymentMethod?.name})`}
|
|
302
302
|
/>
|
|
303
303
|
}
|
|
304
304
|
direction={InfoDirection}
|
|
@@ -201,7 +201,7 @@ export default function CustomerSubscriptionDetail() {
|
|
|
201
201
|
/>
|
|
202
202
|
<InfoRow
|
|
203
203
|
label={t('admin.paymentMethod._name')}
|
|
204
|
-
value={<Currency logo={data.paymentMethod
|
|
204
|
+
value={<Currency logo={data.paymentMethod?.logo} name={data.paymentMethod?.name} />}
|
|
205
205
|
alignItems={InfoAlignItems}
|
|
206
206
|
direction={InfoDirection}
|
|
207
207
|
/>
|