@blocklet/payment-react 1.21.15 → 1.21.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.
- package/.aigne/doc-smith/translation-cache.yaml +11 -0
- package/es/components/over-due-invoice-payment.js +86 -21
- package/es/components/payment-beneficiaries.js +3 -3
- package/es/components/stripe-payment-action.d.ts +16 -0
- package/es/components/stripe-payment-action.js +164 -0
- package/es/history/invoice/list.js +58 -2
- package/es/index.d.ts +3 -1
- package/es/index.js +2 -0
- package/es/libs/util.d.ts +1 -0
- package/es/libs/util.js +16 -0
- package/es/locales/en.js +18 -3
- package/es/locales/zh.js +12 -3
- package/es/payment/form/stripe/form.d.ts +4 -1
- package/es/payment/form/stripe/form.js +9 -5
- package/lib/components/over-due-invoice-payment.js +99 -31
- package/lib/components/payment-beneficiaries.js +3 -2
- package/lib/components/stripe-payment-action.d.ts +16 -0
- package/lib/components/stripe-payment-action.js +191 -0
- package/lib/history/invoice/list.js +58 -10
- package/lib/index.d.ts +3 -1
- package/lib/index.js +8 -0
- package/lib/libs/util.d.ts +1 -0
- package/lib/libs/util.js +17 -0
- package/lib/locales/en.js +18 -3
- package/lib/locales/zh.js +12 -3
- package/lib/payment/form/stripe/form.d.ts +4 -1
- package/lib/payment/form/stripe/form.js +9 -5
- package/package.json +3 -3
- package/src/components/over-due-invoice-payment.tsx +101 -29
- package/src/components/payment-beneficiaries.tsx +3 -3
- package/src/components/stripe-payment-action.tsx +220 -0
- package/src/history/invoice/list.tsx +67 -13
- package/src/index.ts +3 -0
- package/src/libs/util.ts +17 -0
- package/src/locales/en.tsx +16 -0
- package/src/locales/zh.tsx +10 -0
- package/src/payment/form/stripe/form.tsx +9 -2
package/lib/locales/en.js
CHANGED
|
@@ -115,7 +115,8 @@ module.exports = (0, _flat.default)({
|
|
|
115
115
|
cancel: "Cancel"
|
|
116
116
|
},
|
|
117
117
|
paymentMethod: "Payment Method",
|
|
118
|
-
viewInvoice: "View Invoice"
|
|
118
|
+
viewInvoice: "View Invoice",
|
|
119
|
+
submit: "Submit"
|
|
119
120
|
},
|
|
120
121
|
payment: {
|
|
121
122
|
checkout: {
|
|
@@ -453,7 +454,11 @@ module.exports = (0, _flat.default)({
|
|
|
453
454
|
amountApplied: "Applied Credit",
|
|
454
455
|
pay: "Pay this invoice",
|
|
455
456
|
paySuccess: "You have successfully paid the invoice",
|
|
457
|
+
payProcessing: "Payment is being processed, please refresh in a moment",
|
|
456
458
|
payError: "Failed to pay the invoice",
|
|
459
|
+
sync: "Sync Status",
|
|
460
|
+
syncing: "Syncing...",
|
|
461
|
+
syncSuccess: "Synced successfully",
|
|
457
462
|
renew: "Renew the subscription",
|
|
458
463
|
renewSuccess: "You have successfully renewed the subscription",
|
|
459
464
|
renewError: "Failed to renew the subscription",
|
|
@@ -462,7 +467,16 @@ module.exports = (0, _flat.default)({
|
|
|
462
467
|
invoiceNumber: "Invoice Number",
|
|
463
468
|
emptyList: "No Invoices",
|
|
464
469
|
noPaymentRequired: "No Payment Required",
|
|
465
|
-
payBatch: "Pay Due Invoices"
|
|
470
|
+
payBatch: "Pay Due Invoices",
|
|
471
|
+
stripePayDescription: "Complete payment using your saved payment method or add a new one.",
|
|
472
|
+
amount: "Amount",
|
|
473
|
+
paymentConfirmTitle: "Payment Confirmation",
|
|
474
|
+
paymentConfirmDescription: "After completing this payment, the payment method you use will be automatically set as the default for this subscription. Additionally, we will retry payment for any other unpaid invoices associated with this subscription.",
|
|
475
|
+
continue: "Continue"
|
|
476
|
+
},
|
|
477
|
+
overduePayment: {
|
|
478
|
+
setupPaymentDescription: "Use your saved card or add a new one to complete payment via Stripe.",
|
|
479
|
+
totalAmount: "Total Amount"
|
|
466
480
|
},
|
|
467
481
|
payment: {
|
|
468
482
|
empty: "There are no payments",
|
|
@@ -531,7 +545,8 @@ module.exports = (0, _flat.default)({
|
|
|
531
545
|
list: "Past Due Invoices:",
|
|
532
546
|
empty: "There are no overdue invoices for your subscription {name}.",
|
|
533
547
|
retry: "Retry",
|
|
534
|
-
paid: "Paid"
|
|
548
|
+
paid: "Paid",
|
|
549
|
+
processing: "Processing"
|
|
535
550
|
}
|
|
536
551
|
}
|
|
537
552
|
},
|
package/lib/locales/zh.js
CHANGED
|
@@ -115,7 +115,8 @@ module.exports = (0, _flat.default)({
|
|
|
115
115
|
cancel: "\u53D6\u6D88"
|
|
116
116
|
},
|
|
117
117
|
paymentMethod: "\u652F\u4ED8\u65B9\u5F0F",
|
|
118
|
-
viewInvoice: "\u67E5\u770B\u8D26\u5355"
|
|
118
|
+
viewInvoice: "\u67E5\u770B\u8D26\u5355",
|
|
119
|
+
submit: "\u63D0\u4EA4"
|
|
119
120
|
},
|
|
120
121
|
payment: {
|
|
121
122
|
checkout: {
|
|
@@ -457,7 +458,11 @@ module.exports = (0, _flat.default)({
|
|
|
457
458
|
amountApplied: "\u4F59\u989D\u53D8\u66F4",
|
|
458
459
|
pay: "\u652F\u4ED8\u6B64\u8D26\u5355",
|
|
459
460
|
paySuccess: "\u652F\u4ED8\u6210\u529F",
|
|
461
|
+
payProcessing: "\u652F\u4ED8\u5904\u7406\u4E2D\uFF0C\u8BF7\u7A0D\u5019\u5237\u65B0\u67E5\u770B",
|
|
460
462
|
payError: "\u652F\u4ED8\u5931\u8D25",
|
|
463
|
+
sync: "\u540C\u6B65\u72B6\u6001",
|
|
464
|
+
syncing: "\u540C\u6B65\u4E2D...",
|
|
465
|
+
syncSuccess: "\u540C\u6B65\u6210\u529F",
|
|
461
466
|
renew: "\u6062\u590D\u8BA2\u9605",
|
|
462
467
|
renewSuccess: "\u8BA2\u9605\u6062\u590D\u6210\u529F",
|
|
463
468
|
renewError: "\u8BA2\u9605\u6062\u590D\u5931\u8D25",
|
|
@@ -466,7 +471,10 @@ module.exports = (0, _flat.default)({
|
|
|
466
471
|
invoiceNumber: "\u8D26\u5355\u7F16\u53F7",
|
|
467
472
|
emptyList: "\u6CA1\u6709\u8D26\u5355",
|
|
468
473
|
noPaymentRequired: "\u65E0\u9700\u652F\u4ED8",
|
|
469
|
-
payBatch: "\u652F\u4ED8\u6B20\u6B3E"
|
|
474
|
+
payBatch: "\u652F\u4ED8\u6B20\u6B3E",
|
|
475
|
+
paymentConfirmTitle: "\u652F\u4ED8\u786E\u8BA4",
|
|
476
|
+
paymentConfirmDescription: "\u5B8C\u6210\u672C\u6B21\u652F\u4ED8\u540E\uFF0C\u60A8\u4F7F\u7528\u7684\u652F\u4ED8\u65B9\u5F0F\u5C06\u81EA\u52A8\u8BBE\u7F6E\u4E3A\u8BE5\u8BA2\u9605\u7684\u9ED8\u8BA4\u652F\u4ED8\u65B9\u5F0F\u3002\u6B64\u5916\uFF0C\u6211\u4EEC\u8FD8\u5C06\u5BF9\u8BE5\u8BA2\u9605\u7684\u5176\u4ED6\u6B20\u8D39\u8D26\u5355\u8FDB\u884C\u91CD\u8BD5\u6536\u8D39\u3002",
|
|
477
|
+
continue: "\u7EE7\u7EED"
|
|
470
478
|
},
|
|
471
479
|
payment: {
|
|
472
480
|
empty: "\u6CA1\u6709\u652F\u4ED8\u8BB0\u5F55",
|
|
@@ -535,7 +543,8 @@ module.exports = (0, _flat.default)({
|
|
|
535
543
|
list: "\u6B20\u8D39\u8D26\u5355\uFF1A",
|
|
536
544
|
empty: "\u60A8\u7684\u3010{name}\u3011\u8BA2\u9605\u5F53\u524D\u6CA1\u6709\u6B20\u8D39\u8D26\u5355",
|
|
537
545
|
retry: "\u91CD\u65B0\u652F\u4ED8",
|
|
538
|
-
paid: "\u5DF2\u652F\u4ED8"
|
|
546
|
+
paid: "\u5DF2\u652F\u4ED8",
|
|
547
|
+
processing: "\u652F\u4ED8\u4E2D"
|
|
539
548
|
}
|
|
540
549
|
}
|
|
541
550
|
},
|
|
@@ -6,6 +6,7 @@ export type StripeCheckoutFormProps = {
|
|
|
6
6
|
mode: string;
|
|
7
7
|
onConfirm: Function;
|
|
8
8
|
returnUrl?: string;
|
|
9
|
+
submitButtonText?: string;
|
|
9
10
|
};
|
|
10
11
|
export type StripeCheckoutProps = {
|
|
11
12
|
clientSecret: string;
|
|
@@ -16,5 +17,7 @@ export type StripeCheckoutProps = {
|
|
|
16
17
|
onConfirm: Function;
|
|
17
18
|
onCancel: Function;
|
|
18
19
|
returnUrl?: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
submitButtonText?: string;
|
|
19
22
|
};
|
|
20
|
-
export default function StripeCheckout({ clientSecret, intentType, publicKey, mode, customer, onConfirm, onCancel, returnUrl, }: StripeCheckoutProps): import("react").JSX.Element;
|
|
23
|
+
export default function StripeCheckout({ clientSecret, intentType, publicKey, mode, customer, onConfirm, onCancel, returnUrl, title, submitButtonText, }: StripeCheckoutProps): import("react").JSX.Element;
|
|
@@ -37,7 +37,8 @@ function StripeCheckoutForm({
|
|
|
37
37
|
customer,
|
|
38
38
|
mode,
|
|
39
39
|
onConfirm,
|
|
40
|
-
returnUrl = ""
|
|
40
|
+
returnUrl = "",
|
|
41
|
+
submitButtonText = ""
|
|
41
42
|
}) {
|
|
42
43
|
const stripe = useStripe();
|
|
43
44
|
const elements = useElements();
|
|
@@ -247,7 +248,7 @@ function StripeCheckoutForm({
|
|
|
247
248
|
variant: "contained",
|
|
248
249
|
color: "primary",
|
|
249
250
|
size: "large",
|
|
250
|
-
children: t("payment.checkout.continue", {
|
|
251
|
+
children: submitButtonText || t("payment.checkout.continue", {
|
|
251
252
|
action: t(`payment.checkout.${mode}`)
|
|
252
253
|
})
|
|
253
254
|
}), state.message && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
@@ -276,7 +277,9 @@ function StripeCheckout({
|
|
|
276
277
|
customer,
|
|
277
278
|
onConfirm,
|
|
278
279
|
onCancel,
|
|
279
|
-
returnUrl = ""
|
|
280
|
+
returnUrl = "",
|
|
281
|
+
title = "",
|
|
282
|
+
submitButtonText = ""
|
|
280
283
|
}) {
|
|
281
284
|
const stripePromise = loadStripe(publicKey);
|
|
282
285
|
const {
|
|
@@ -301,7 +304,7 @@ function StripeCheckout({
|
|
|
301
304
|
onCancel();
|
|
302
305
|
};
|
|
303
306
|
return /* @__PURE__ */(0, _jsxRuntime.jsx)(_Dialog.default, {
|
|
304
|
-
title: t("payment.checkout.cardPay", {
|
|
307
|
+
title: title || t("payment.checkout.cardPay", {
|
|
305
308
|
action: t(`payment.checkout.${mode}`)
|
|
306
309
|
}),
|
|
307
310
|
showCloseButton: state.closable,
|
|
@@ -352,7 +355,8 @@ function StripeCheckout({
|
|
|
352
355
|
mode,
|
|
353
356
|
customer,
|
|
354
357
|
onConfirm,
|
|
355
|
-
returnUrl
|
|
358
|
+
returnUrl,
|
|
359
|
+
submitButtonText
|
|
356
360
|
})
|
|
357
361
|
})
|
|
358
362
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/payment-react",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.17",
|
|
4
4
|
"description": "Reusable react components for payment kit v2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"@babel/core": "^7.27.4",
|
|
95
95
|
"@babel/preset-env": "^7.27.2",
|
|
96
96
|
"@babel/preset-react": "^7.27.1",
|
|
97
|
-
"@blocklet/payment-types": "1.21.
|
|
97
|
+
"@blocklet/payment-types": "1.21.17",
|
|
98
98
|
"@storybook/addon-essentials": "^7.6.20",
|
|
99
99
|
"@storybook/addon-interactions": "^7.6.20",
|
|
100
100
|
"@storybook/addon-links": "^7.6.20",
|
|
@@ -125,5 +125,5 @@
|
|
|
125
125
|
"vite-plugin-babel": "^1.3.1",
|
|
126
126
|
"vite-plugin-node-polyfills": "^0.23.0"
|
|
127
127
|
},
|
|
128
|
-
"gitHead": "
|
|
128
|
+
"gitHead": "a823bc05e706681ee70451437b0460aba909c253"
|
|
129
129
|
}
|
|
@@ -22,6 +22,7 @@ import { formatAmount, formatError, getPrefix, isCrossOrigin } from '../libs/uti
|
|
|
22
22
|
import { useSubscription } from '../hooks/subscription';
|
|
23
23
|
import api from '../libs/api';
|
|
24
24
|
import LoadingButton from './loading-button';
|
|
25
|
+
import StripePaymentAction from './stripe-payment-action';
|
|
25
26
|
|
|
26
27
|
type DialogProps = {
|
|
27
28
|
open?: boolean;
|
|
@@ -112,12 +113,20 @@ function OverdueInvoicePayment({
|
|
|
112
113
|
const [payLoading, setPayLoading] = useState(false);
|
|
113
114
|
const [dialogOpen, setDialogOpen] = useState(dialogProps.open || false);
|
|
114
115
|
const [processedCurrencies, setProcessedCurrencies] = useState<{ [key: string]: number }>({});
|
|
115
|
-
const [paymentStatus, setPaymentStatus] = useState<{ [key: string]: 'success' | 'error' | 'idle' }>(
|
|
116
|
+
const [paymentStatus, setPaymentStatus] = useState<{ [key: string]: 'success' | 'error' | 'idle' | 'processing' }>(
|
|
117
|
+
{}
|
|
118
|
+
);
|
|
119
|
+
const [stripePaymentInProgress, setStripePaymentInProgress] = useState<{ [key: string]: boolean }>({});
|
|
120
|
+
const stripePaymentInProgressRef = useRef(stripePaymentInProgress);
|
|
116
121
|
|
|
117
122
|
const sourceType = subscriptionId ? 'subscription' : 'customer';
|
|
118
123
|
const effectiveCustomerId = customerId || session?.user?.did;
|
|
119
124
|
const sourceId = subscriptionId || effectiveCustomerId;
|
|
120
125
|
const customerIdRef = useRef(effectiveCustomerId);
|
|
126
|
+
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
stripePaymentInProgressRef.current = stripePaymentInProgress;
|
|
129
|
+
}, [stripePaymentInProgress]);
|
|
121
130
|
const {
|
|
122
131
|
data = {
|
|
123
132
|
summary: {},
|
|
@@ -154,6 +163,36 @@ function OverdueInvoicePayment({
|
|
|
154
163
|
|
|
155
164
|
const debouncedHandleInvoicePaid = debounce(
|
|
156
165
|
async (currencyId: string) => {
|
|
166
|
+
// If Stripe payment is in progress, check if it's complete before refreshing
|
|
167
|
+
if (stripePaymentInProgressRef.current[currencyId]) {
|
|
168
|
+
try {
|
|
169
|
+
const checkData = await fetchOverdueInvoices({
|
|
170
|
+
subscriptionId,
|
|
171
|
+
customerId: effectiveCustomerId,
|
|
172
|
+
authToken,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const hasRemainingInvoices = checkData.invoices?.some((inv: Invoice) => inv.currency_id === currencyId);
|
|
176
|
+
|
|
177
|
+
// Only refresh UI when all invoices are paid
|
|
178
|
+
if (hasRemainingInvoices) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Clear Stripe payment state
|
|
183
|
+
setStripePaymentInProgress((prev) => {
|
|
184
|
+
const newState = { ...prev };
|
|
185
|
+
delete newState[currencyId];
|
|
186
|
+
return newState;
|
|
187
|
+
});
|
|
188
|
+
setPaymentStatus((prev) => ({ ...prev, [currencyId]: 'success' }));
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.error('Error checking Stripe payment completion:', err);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Now refresh and update UI
|
|
157
196
|
if (successToast) {
|
|
158
197
|
Toast.close();
|
|
159
198
|
Toast.success(t('payment.customer.invoice.paySuccess'));
|
|
@@ -189,7 +228,7 @@ function OverdueInvoicePayment({
|
|
|
189
228
|
return isPaid;
|
|
190
229
|
};
|
|
191
230
|
|
|
192
|
-
const handleConnected = async () => {
|
|
231
|
+
const handleConnected = async (currencyId: string, isStripe = false) => {
|
|
193
232
|
if (isCrossOriginRequest) {
|
|
194
233
|
try {
|
|
195
234
|
const paid = await waitForInvoiceAllPaid();
|
|
@@ -199,32 +238,43 @@ function OverdueInvoicePayment({
|
|
|
199
238
|
}
|
|
200
239
|
if (paid) {
|
|
201
240
|
setDialogOpen(false);
|
|
202
|
-
onPaid(sourceId as string,
|
|
241
|
+
onPaid(sourceId as string, currencyId, sourceType as 'subscription' | 'customer');
|
|
203
242
|
}
|
|
204
243
|
} catch (err) {
|
|
205
244
|
console.error('Check payment status failed:', err);
|
|
206
245
|
}
|
|
246
|
+
} else if (isStripe) {
|
|
247
|
+
setStripePaymentInProgress((prev) => ({ ...prev, [currencyId]: true }));
|
|
248
|
+
setPaymentStatus((prev) => ({ ...prev, [currencyId]: 'processing' }));
|
|
207
249
|
}
|
|
208
250
|
};
|
|
209
251
|
|
|
210
252
|
useEffect(() => {
|
|
211
|
-
if (subscription
|
|
212
|
-
|
|
213
|
-
const relevantId = subscriptionId || response.customer_id;
|
|
214
|
-
const uniqueKey = `${relevantId}-${response.currency_id}`;
|
|
215
|
-
|
|
216
|
-
if (
|
|
217
|
-
(subscriptionId && response.subscription_id === subscriptionId) ||
|
|
218
|
-
(effectiveCustomerId && effectiveCustomerId === response.customer_id) ||
|
|
219
|
-
(customerIdRef.current && customerIdRef.current === response.customer_id)
|
|
220
|
-
) {
|
|
221
|
-
if (!processedCurrencies[uniqueKey]) {
|
|
222
|
-
setProcessedCurrencies((prev) => ({ ...prev, [uniqueKey]: 1 }));
|
|
223
|
-
debouncedHandleInvoicePaid(response.currency_id);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
});
|
|
253
|
+
if (!subscription || isCrossOriginRequest) {
|
|
254
|
+
return undefined;
|
|
227
255
|
}
|
|
256
|
+
|
|
257
|
+
const handleInvoicePaid = ({ response }: { response: TInvoiceExpanded }) => {
|
|
258
|
+
const relevantId = subscriptionId || response.customer_id;
|
|
259
|
+
const uniqueKey = `${relevantId}-${response.currency_id}`;
|
|
260
|
+
|
|
261
|
+
if (
|
|
262
|
+
(subscriptionId && response.subscription_id === subscriptionId) ||
|
|
263
|
+
(effectiveCustomerId && effectiveCustomerId === response.customer_id) ||
|
|
264
|
+
(customerIdRef.current && customerIdRef.current === response.customer_id)
|
|
265
|
+
) {
|
|
266
|
+
if (!processedCurrencies[uniqueKey]) {
|
|
267
|
+
setProcessedCurrencies((prev) => ({ ...prev, [uniqueKey]: 1 }));
|
|
268
|
+
debouncedHandleInvoicePaid(response.currency_id);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
subscription.on('invoice.paid', handleInvoicePaid);
|
|
274
|
+
|
|
275
|
+
return () => {
|
|
276
|
+
subscription.off('invoice.paid', handleInvoicePaid);
|
|
277
|
+
};
|
|
228
278
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
229
279
|
}, [subscription, subscriptionId, effectiveCustomerId]);
|
|
230
280
|
|
|
@@ -268,7 +318,7 @@ function OverdueInvoicePayment({
|
|
|
268
318
|
} as any,
|
|
269
319
|
onSuccess: () => {
|
|
270
320
|
connect.close();
|
|
271
|
-
handleConnected();
|
|
321
|
+
handleConnected(currency.id);
|
|
272
322
|
setPayLoading(false);
|
|
273
323
|
setPaymentStatus((prev) => ({
|
|
274
324
|
...prev,
|
|
@@ -325,7 +375,7 @@ function OverdueInvoicePayment({
|
|
|
325
375
|
const renderPayButton = (
|
|
326
376
|
item: SummaryItem,
|
|
327
377
|
primaryButton = true,
|
|
328
|
-
|
|
378
|
+
options: {
|
|
329
379
|
variant?: 'contained' | 'text';
|
|
330
380
|
sx?: SxProps;
|
|
331
381
|
} = {
|
|
@@ -339,8 +389,7 @@ function OverdueInvoicePayment({
|
|
|
339
389
|
if (status === 'success') {
|
|
340
390
|
return (
|
|
341
391
|
<Button
|
|
342
|
-
|
|
343
|
-
variant={props?.variant || 'contained'}
|
|
392
|
+
variant={options?.variant || 'contained'}
|
|
344
393
|
size="small"
|
|
345
394
|
{...(primaryButton
|
|
346
395
|
? {}
|
|
@@ -355,7 +404,7 @@ function OverdueInvoicePayment({
|
|
|
355
404
|
|
|
356
405
|
if (status === 'error') {
|
|
357
406
|
return (
|
|
358
|
-
<Button variant=
|
|
407
|
+
<Button variant={options?.variant || 'contained'} size="small" onClick={() => handlePay(item)} sx={options?.sx}>
|
|
359
408
|
{t('payment.subscription.overdue.retry')}
|
|
360
409
|
</Button>
|
|
361
410
|
);
|
|
@@ -363,19 +412,42 @@ function OverdueInvoicePayment({
|
|
|
363
412
|
|
|
364
413
|
if (item.method.type === 'stripe') {
|
|
365
414
|
return (
|
|
366
|
-
<
|
|
367
|
-
{
|
|
368
|
-
|
|
415
|
+
<StripePaymentAction
|
|
416
|
+
subscriptionId={subscriptionId}
|
|
417
|
+
customerId={!subscriptionId ? effectiveCustomerId : undefined}
|
|
418
|
+
currencyId={currency.id}
|
|
419
|
+
paymentMethod={item.method}
|
|
420
|
+
onSuccess={() => {
|
|
421
|
+
handleConnected(currency.id, true);
|
|
422
|
+
}}
|
|
423
|
+
onError={() => {
|
|
424
|
+
setPaymentStatus((prev) => ({ ...prev, [currency.id]: 'error' }));
|
|
425
|
+
setStripePaymentInProgress((prev) => ({ ...prev, [currency.id]: false }));
|
|
426
|
+
}}>
|
|
427
|
+
{(onPay: () => void, paying: boolean) => (
|
|
428
|
+
<LoadingButton
|
|
429
|
+
variant={options?.variant || 'contained'}
|
|
430
|
+
size="small"
|
|
431
|
+
disabled={paying || status === 'processing'}
|
|
432
|
+
loading={paying || status === 'processing'}
|
|
433
|
+
onClick={onPay}
|
|
434
|
+
sx={options?.sx}>
|
|
435
|
+
{status === 'processing'
|
|
436
|
+
? t('payment.subscription.overdue.processing')
|
|
437
|
+
: t('payment.subscription.overdue.payNow')}
|
|
438
|
+
</LoadingButton>
|
|
439
|
+
)}
|
|
440
|
+
</StripePaymentAction>
|
|
369
441
|
);
|
|
370
442
|
}
|
|
371
443
|
return (
|
|
372
444
|
<LoadingButton
|
|
373
|
-
variant=
|
|
445
|
+
variant={options?.variant || 'contained'}
|
|
374
446
|
size="small"
|
|
375
447
|
disabled={inProcess}
|
|
376
448
|
loading={inProcess}
|
|
377
449
|
onClick={() => handlePay(item)}
|
|
378
|
-
{
|
|
450
|
+
sx={options?.sx}>
|
|
379
451
|
{t('payment.subscription.overdue.payNow')}
|
|
380
452
|
</LoadingButton>
|
|
381
453
|
);
|
|
@@ -3,7 +3,7 @@ import { Avatar, Box, Stack, Typography } from '@mui/material';
|
|
|
3
3
|
import { BN } from '@ocap/util';
|
|
4
4
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
5
5
|
import DID from '@arcblock/ux/lib/DID';
|
|
6
|
-
import { formatBNStr } from '../libs/util';
|
|
6
|
+
import { formatBNStr, formatLinkWithLocale } from '../libs/util';
|
|
7
7
|
|
|
8
8
|
export interface TBeneficiary {
|
|
9
9
|
name: string;
|
|
@@ -26,7 +26,7 @@ interface BenefitsProps {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export default function PaymentBeneficiaries({ data, currency, totalAmount = '0' }: BenefitsProps) {
|
|
29
|
-
const { t } = useLocaleContext();
|
|
29
|
+
const { t, locale } = useLocaleContext();
|
|
30
30
|
return (
|
|
31
31
|
<Stack spacing={2}>
|
|
32
32
|
<Typography
|
|
@@ -78,7 +78,7 @@ export default function PaymentBeneficiaries({ data, currency, totalAmount = '0'
|
|
|
78
78
|
variant="subtitle2"
|
|
79
79
|
onClick={() => {
|
|
80
80
|
if (item.url) {
|
|
81
|
-
window.open(item.url, '_blank');
|
|
81
|
+
window.open(formatLinkWithLocale(item.url, locale), '_blank');
|
|
82
82
|
}
|
|
83
83
|
}}
|
|
84
84
|
sx={{
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/* eslint-disable react/require-default-props */
|
|
2
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
+
import Toast from '@arcblock/ux/lib/Toast';
|
|
4
|
+
import { Dialog } from '@arcblock/ux';
|
|
5
|
+
import type { Customer, TPaymentMethod, TInvoiceExpanded } from '@blocklet/payment-types';
|
|
6
|
+
import { Button, Typography } from '@mui/material';
|
|
7
|
+
import { useSetState } from 'ahooks';
|
|
8
|
+
import { useEffect, useRef } from 'react';
|
|
9
|
+
|
|
10
|
+
import StripeForm from '../payment/form/stripe';
|
|
11
|
+
import api from '../libs/api';
|
|
12
|
+
import { formatError } from '../libs/util';
|
|
13
|
+
|
|
14
|
+
export interface StripePaymentActionProps {
|
|
15
|
+
invoice?: TInvoiceExpanded;
|
|
16
|
+
invoiceIds?: string[];
|
|
17
|
+
subscriptionId?: string;
|
|
18
|
+
customerId?: string;
|
|
19
|
+
currencyId?: string;
|
|
20
|
+
paymentMethod?: TPaymentMethod;
|
|
21
|
+
|
|
22
|
+
autoTrigger?: boolean;
|
|
23
|
+
onExternalPayment?: (invoiceId?: string) => void;
|
|
24
|
+
|
|
25
|
+
onSuccess?: () => void;
|
|
26
|
+
onError?: (error: Error) => void;
|
|
27
|
+
onClose?: () => void;
|
|
28
|
+
|
|
29
|
+
children?: (onPay: () => void, loading: boolean) => React.ReactNode;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default function StripePaymentAction(props: StripePaymentActionProps) {
|
|
33
|
+
const {
|
|
34
|
+
invoice,
|
|
35
|
+
invoiceIds,
|
|
36
|
+
subscriptionId,
|
|
37
|
+
customerId,
|
|
38
|
+
currencyId,
|
|
39
|
+
paymentMethod,
|
|
40
|
+
autoTrigger = false,
|
|
41
|
+
onExternalPayment,
|
|
42
|
+
onSuccess,
|
|
43
|
+
onError,
|
|
44
|
+
onClose,
|
|
45
|
+
children,
|
|
46
|
+
} = props;
|
|
47
|
+
const { t } = useLocaleContext();
|
|
48
|
+
|
|
49
|
+
const [state, setState] = useSetState<{
|
|
50
|
+
paying: boolean;
|
|
51
|
+
confirmDialog: boolean;
|
|
52
|
+
stripeDialog: boolean;
|
|
53
|
+
clientSecret: string | null;
|
|
54
|
+
publishableKey: string | null;
|
|
55
|
+
customer: Customer | null;
|
|
56
|
+
}>({
|
|
57
|
+
paying: false,
|
|
58
|
+
confirmDialog: false,
|
|
59
|
+
stripeDialog: false,
|
|
60
|
+
clientSecret: null,
|
|
61
|
+
publishableKey: null,
|
|
62
|
+
customer: null,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const autoTriggerRef = useRef(false);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (autoTrigger && !autoTriggerRef.current) {
|
|
69
|
+
autoTriggerRef.current = true;
|
|
70
|
+
handlePay();
|
|
71
|
+
}
|
|
72
|
+
}, [autoTrigger]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
73
|
+
|
|
74
|
+
const handlePay = async () => {
|
|
75
|
+
if (state.paying) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const hasSubscription = !!(subscriptionId || invoice?.subscription_id);
|
|
80
|
+
const method = paymentMethod || invoice?.paymentMethod;
|
|
81
|
+
const shouldShowConfirm = hasSubscription && method?.type === 'stripe';
|
|
82
|
+
|
|
83
|
+
if (shouldShowConfirm) {
|
|
84
|
+
setState({ confirmDialog: true });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await proceedWithPayment();
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const proceedWithPayment = async () => {
|
|
92
|
+
setState({ paying: true, confirmDialog: false });
|
|
93
|
+
|
|
94
|
+
const derivedCurrencyId = currencyId || invoice?.currency_id || invoice?.paymentCurrency?.id;
|
|
95
|
+
const derivedPaymentMethod = paymentMethod || invoice?.paymentMethod;
|
|
96
|
+
const isStripePayment = derivedPaymentMethod?.type === 'stripe';
|
|
97
|
+
|
|
98
|
+
if (isStripePayment && derivedCurrencyId) {
|
|
99
|
+
const stripePayload: Record<string, any> = {};
|
|
100
|
+
|
|
101
|
+
if (invoiceIds && invoiceIds.length > 0) {
|
|
102
|
+
stripePayload.invoice_ids = invoiceIds;
|
|
103
|
+
} else if (invoice) {
|
|
104
|
+
stripePayload.invoice_ids = [invoice.id];
|
|
105
|
+
} else if (subscriptionId) {
|
|
106
|
+
stripePayload.subscription_id = subscriptionId;
|
|
107
|
+
} else if (customerId) {
|
|
108
|
+
stripePayload.customer_id = customerId;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (derivedCurrencyId) {
|
|
112
|
+
stripePayload.currency_id = derivedCurrencyId;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const { data: paymentData } = await api.post('/api/invoices/pay-stripe', stripePayload);
|
|
117
|
+
setState({
|
|
118
|
+
paying: false,
|
|
119
|
+
stripeDialog: true,
|
|
120
|
+
clientSecret: paymentData.client_secret,
|
|
121
|
+
publishableKey: paymentData.publishable_key,
|
|
122
|
+
customer: paymentData.customer || null,
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
} catch (err: any) {
|
|
126
|
+
const error = formatError(err);
|
|
127
|
+
Toast.error(error);
|
|
128
|
+
setState({ paying: false });
|
|
129
|
+
onError?.(error);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setState({ paying: false });
|
|
135
|
+
if (onExternalPayment) {
|
|
136
|
+
onExternalPayment(invoice?.id);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Toast.error(t('payment.customer.invoice.payError'));
|
|
141
|
+
onError?.(new Error('EXTERNAL_PAYMENT_HANDLER_NOT_PROVIDED'));
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const handleConfirmCancel = () => {
|
|
145
|
+
setState({ confirmDialog: false, paying: false });
|
|
146
|
+
onClose?.();
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const handleStripeConfirm = () => {
|
|
150
|
+
Toast.success(t('payment.customer.invoice.payProcessing'));
|
|
151
|
+
setState({
|
|
152
|
+
paying: false,
|
|
153
|
+
stripeDialog: false,
|
|
154
|
+
clientSecret: null,
|
|
155
|
+
publishableKey: null,
|
|
156
|
+
customer: null,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
setTimeout(() => {
|
|
160
|
+
onSuccess?.();
|
|
161
|
+
}, 2000);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const handleStripeCancel = () => {
|
|
165
|
+
setState({
|
|
166
|
+
paying: false,
|
|
167
|
+
stripeDialog: false,
|
|
168
|
+
clientSecret: null,
|
|
169
|
+
publishableKey: null,
|
|
170
|
+
customer: null,
|
|
171
|
+
});
|
|
172
|
+
onClose?.();
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<>
|
|
177
|
+
{children?.(handlePay, state.paying)}
|
|
178
|
+
|
|
179
|
+
{state.confirmDialog && (
|
|
180
|
+
<Dialog
|
|
181
|
+
open={state.confirmDialog}
|
|
182
|
+
title={t('payment.customer.invoice.paymentConfirmTitle')}
|
|
183
|
+
onClose={handleConfirmCancel}
|
|
184
|
+
maxWidth="sm"
|
|
185
|
+
PaperProps={{
|
|
186
|
+
style: {
|
|
187
|
+
minHeight: 0,
|
|
188
|
+
},
|
|
189
|
+
}}
|
|
190
|
+
actions={[
|
|
191
|
+
<Button key="cancel" variant="outlined" onClick={handleConfirmCancel}>
|
|
192
|
+
{t('common.cancel')}
|
|
193
|
+
</Button>,
|
|
194
|
+
<Button key="continue" variant="contained" onClick={proceedWithPayment}>
|
|
195
|
+
{t('payment.customer.invoice.continue')}
|
|
196
|
+
</Button>,
|
|
197
|
+
]}>
|
|
198
|
+
<Typography variant="body1" sx={{ color: 'text.secondary', mt: -2 }}>
|
|
199
|
+
{t('payment.customer.invoice.paymentConfirmDescription')}
|
|
200
|
+
</Typography>
|
|
201
|
+
</Dialog>
|
|
202
|
+
)}
|
|
203
|
+
|
|
204
|
+
{state.stripeDialog && state.clientSecret && state.publishableKey && state.customer && (
|
|
205
|
+
<StripeForm
|
|
206
|
+
clientSecret={state.clientSecret}
|
|
207
|
+
intentType="setup_intent"
|
|
208
|
+
publicKey={state.publishableKey}
|
|
209
|
+
customer={state.customer}
|
|
210
|
+
mode="setup"
|
|
211
|
+
title={t('payment.customer.invoice.pay')}
|
|
212
|
+
submitButtonText={t('common.submit')}
|
|
213
|
+
onConfirm={handleStripeConfirm}
|
|
214
|
+
onCancel={handleStripeCancel}
|
|
215
|
+
returnUrl={window.location.href}
|
|
216
|
+
/>
|
|
217
|
+
)}
|
|
218
|
+
</>
|
|
219
|
+
);
|
|
220
|
+
}
|