payment-kit 1.13.22 → 1.13.23
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/integrations/stripe/handlers/invoice.ts +129 -101
- package/api/src/jobs/event.ts +18 -10
- package/api/src/jobs/invoice.ts +8 -8
- package/api/src/jobs/payment.ts +1 -1
- package/api/src/jobs/subscription.ts +1 -1
- package/api/src/jobs/webhook.ts +9 -11
- package/api/src/store/models/types.ts +1 -0
- package/blocklet.yml +1 -1
- package/package.json +3 -3
- package/src/components/actions.tsx +4 -10
- package/src/components/blockchain/tx.tsx +30 -9
- package/src/components/click-boundary.tsx +7 -0
- package/src/components/confirm.tsx +2 -18
- package/src/components/customer/actions.tsx +3 -2
- package/src/components/invoice/action.tsx +3 -2
- package/src/components/payment-intent/actions.tsx +4 -3
- package/src/components/payment-intent/list.tsx +2 -2
- package/src/components/payment-link/actions.tsx +3 -2
- package/src/components/payment-link/item.tsx +18 -15
- package/src/components/payment-method/stripe.tsx +7 -4
- package/src/components/price/actions.tsx +9 -6
- package/src/components/product/actions.tsx +3 -2
- package/src/components/subscription/actions/index.tsx +3 -2
- package/src/components/subscription/items/actions.tsx +17 -14
- package/src/libs/util.ts +10 -0
- package/src/locales/en.tsx +4 -0
- package/src/pages/admin/billing/subscriptions/detail.tsx +1 -1
- package/src/pages/admin/payments/intents/detail.tsx +1 -6
- package/src/pages/admin/settings/payment-methods/create.tsx +3 -0
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
Customer,
|
|
10
10
|
Invoice,
|
|
11
11
|
InvoiceItem,
|
|
12
|
+
PaymentMethod,
|
|
12
13
|
Subscription,
|
|
13
14
|
SubscriptionItem,
|
|
14
15
|
TEventExpanded,
|
|
@@ -29,6 +30,109 @@ export async function handleStripeInvoicePaid(invoice: Invoice, event: TEventExp
|
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
export async function ensureStripeInvoice(stripeInvoice: any, subscription: Subscription, client: Stripe) {
|
|
34
|
+
const customer = await Customer.findByPk(subscription.customer_id);
|
|
35
|
+
const checkoutSession = await CheckoutSession.findOne({ where: { subscription_id: subscription.id } });
|
|
36
|
+
|
|
37
|
+
let invoice = await Invoice.findOne({ where: { 'metadata.stripe_id': stripeInvoice.id } });
|
|
38
|
+
if (invoice) {
|
|
39
|
+
return invoice;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
invoice = await Invoice.create({
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
number: await customer.getInvoiceNumber(),
|
|
46
|
+
...pick(stripeInvoice, [
|
|
47
|
+
'amount_due',
|
|
48
|
+
'amount_paid',
|
|
49
|
+
'amount_remaining',
|
|
50
|
+
'amount_shipping',
|
|
51
|
+
'attempt_count',
|
|
52
|
+
'attempted',
|
|
53
|
+
'auto_advance',
|
|
54
|
+
'billing_reason',
|
|
55
|
+
'collection_method',
|
|
56
|
+
'custom_fields',
|
|
57
|
+
'customer_address',
|
|
58
|
+
'customer_email',
|
|
59
|
+
'customer_name',
|
|
60
|
+
'customer_phone',
|
|
61
|
+
'description',
|
|
62
|
+
'discounts',
|
|
63
|
+
'due_date',
|
|
64
|
+
'effective_at',
|
|
65
|
+
'ending_balance',
|
|
66
|
+
'livemode',
|
|
67
|
+
'paid_out_of_band',
|
|
68
|
+
'paid',
|
|
69
|
+
'period_end',
|
|
70
|
+
'period_start',
|
|
71
|
+
'starting_balance',
|
|
72
|
+
'status_transitions',
|
|
73
|
+
'status',
|
|
74
|
+
'subtotal_excluding_tax',
|
|
75
|
+
'subtotal',
|
|
76
|
+
'tax',
|
|
77
|
+
'total_discount_amounts',
|
|
78
|
+
'total',
|
|
79
|
+
]),
|
|
80
|
+
|
|
81
|
+
currency_id: subscription.currency_id,
|
|
82
|
+
customer_id: subscription.customer_id,
|
|
83
|
+
default_payment_method_id: subscription.default_payment_method_id as string,
|
|
84
|
+
payment_intent_id: '',
|
|
85
|
+
subscription_id: subscription.id,
|
|
86
|
+
checkout_session_id: checkoutSession?.id,
|
|
87
|
+
statement_descriptor: stripeInvoice.statement_descriptor || '',
|
|
88
|
+
|
|
89
|
+
payment_settings: subscription.payment_settings,
|
|
90
|
+
metadata: {
|
|
91
|
+
stripe_id: stripeInvoice.id,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
await client.invoices.update(stripeInvoice.id, { metadata: { appPid: env.appPid, id: invoice.id } });
|
|
95
|
+
logger.info('stripe invoice mirrored', { local: invoice.id, remote: stripeInvoice.id });
|
|
96
|
+
|
|
97
|
+
await Promise.all(
|
|
98
|
+
stripeInvoice.lines.data.map(async (line: any) => {
|
|
99
|
+
const subscriptionItem = line.subscription_item
|
|
100
|
+
? await SubscriptionItem.findOne({ where: { 'metadata.stripe_id': line.subscription_item } })
|
|
101
|
+
: null;
|
|
102
|
+
|
|
103
|
+
// @ts-ignore
|
|
104
|
+
const item = await InvoiceItem.create({
|
|
105
|
+
currency_id: subscription.currency_id,
|
|
106
|
+
customer_id: subscription.customer_id,
|
|
107
|
+
price_id: line.price?.metadata.id as string,
|
|
108
|
+
invoice_id: invoice?.id as string,
|
|
109
|
+
subscription_id: subscription.id,
|
|
110
|
+
subscription_item_id: subscriptionItem?.id,
|
|
111
|
+
...pick(line, [
|
|
112
|
+
'livemode',
|
|
113
|
+
'amount',
|
|
114
|
+
'quantity',
|
|
115
|
+
'description',
|
|
116
|
+
'period',
|
|
117
|
+
'discountable',
|
|
118
|
+
'discount_amounts',
|
|
119
|
+
'discounts',
|
|
120
|
+
'proration',
|
|
121
|
+
'proration_details',
|
|
122
|
+
]),
|
|
123
|
+
metadata: {
|
|
124
|
+
stripe_id: line.id,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
logger.info('stripe invoice items mirrored', { local: item.id, remote: line.id });
|
|
129
|
+
return item;
|
|
130
|
+
})
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
return invoice;
|
|
134
|
+
}
|
|
135
|
+
|
|
32
136
|
export async function handleStripeInvoiceCreated(event: TEventExpanded, client: Stripe) {
|
|
33
137
|
if (['invoice.created', 'payment_intent.created'].includes(event.type) === false) {
|
|
34
138
|
logger.warn('abort because event type not expected', { id: event.id, type: event.type });
|
|
@@ -74,98 +178,7 @@ export async function handleStripeInvoiceCreated(event: TEventExpanded, client:
|
|
|
74
178
|
const checkoutSession = await CheckoutSession.findOne({ where: { subscription_id: subscription.id } });
|
|
75
179
|
|
|
76
180
|
// create stripe invoice
|
|
77
|
-
|
|
78
|
-
if (!invoice) {
|
|
79
|
-
invoice = await Invoice.create({
|
|
80
|
-
// @ts-ignore
|
|
81
|
-
number: await customer.getInvoiceNumber(),
|
|
82
|
-
...pick(stripeInvoice, [
|
|
83
|
-
'amount_due',
|
|
84
|
-
'amount_paid',
|
|
85
|
-
'amount_remaining',
|
|
86
|
-
'amount_shipping',
|
|
87
|
-
'attempt_count',
|
|
88
|
-
'attempted',
|
|
89
|
-
'auto_advance',
|
|
90
|
-
'billing_reason',
|
|
91
|
-
'collection_method',
|
|
92
|
-
'custom_fields',
|
|
93
|
-
'customer_address',
|
|
94
|
-
'customer_email',
|
|
95
|
-
'customer_name',
|
|
96
|
-
'customer_phone',
|
|
97
|
-
'description',
|
|
98
|
-
'discounts',
|
|
99
|
-
'due_date',
|
|
100
|
-
'effective_at',
|
|
101
|
-
'ending_balance',
|
|
102
|
-
'livemode',
|
|
103
|
-
'paid_out_of_band',
|
|
104
|
-
'paid',
|
|
105
|
-
'period_end',
|
|
106
|
-
'period_start',
|
|
107
|
-
'starting_balance',
|
|
108
|
-
'status_transitions',
|
|
109
|
-
'status',
|
|
110
|
-
'subtotal_excluding_tax',
|
|
111
|
-
'subtotal',
|
|
112
|
-
'tax',
|
|
113
|
-
'total_discount_amounts',
|
|
114
|
-
'total',
|
|
115
|
-
]),
|
|
116
|
-
|
|
117
|
-
currency_id: subscription.currency_id,
|
|
118
|
-
customer_id: subscription.customer_id,
|
|
119
|
-
default_payment_method_id: subscription.default_payment_method_id as string,
|
|
120
|
-
payment_intent_id: '',
|
|
121
|
-
subscription_id: subscription.id,
|
|
122
|
-
checkout_session_id: checkoutSession?.id,
|
|
123
|
-
statement_descriptor: stripeInvoice.statement_descriptor || '',
|
|
124
|
-
|
|
125
|
-
payment_settings: subscription.payment_settings,
|
|
126
|
-
metadata: {
|
|
127
|
-
stripe_id: stripeInvoice.id,
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
await client.invoices.update(stripeInvoice.id, { metadata: { appPid: env.appPid, id: invoice.id } });
|
|
131
|
-
logger.info('stripe invoice mirrored', { local: invoice.id, remote: stripeInvoice.id });
|
|
132
|
-
|
|
133
|
-
await Promise.all(
|
|
134
|
-
stripeInvoice.lines.data.map(async (line) => {
|
|
135
|
-
const subscriptionItem = line.subscription_item
|
|
136
|
-
? await SubscriptionItem.findOne({ where: { 'metadata.stripe_id': line.subscription_item } })
|
|
137
|
-
: null;
|
|
138
|
-
|
|
139
|
-
// @ts-ignore
|
|
140
|
-
const item = await InvoiceItem.create({
|
|
141
|
-
currency_id: subscription.currency_id,
|
|
142
|
-
customer_id: subscription.customer_id,
|
|
143
|
-
price_id: line.price?.metadata.id as string,
|
|
144
|
-
invoice_id: invoice?.id as string,
|
|
145
|
-
subscription_id: subscription.id,
|
|
146
|
-
subscription_item_id: subscriptionItem?.id,
|
|
147
|
-
...pick(line, [
|
|
148
|
-
'livemode',
|
|
149
|
-
'amount',
|
|
150
|
-
'quantity',
|
|
151
|
-
'description',
|
|
152
|
-
'period',
|
|
153
|
-
'discountable',
|
|
154
|
-
'discount_amounts',
|
|
155
|
-
'discounts',
|
|
156
|
-
'proration',
|
|
157
|
-
'proration_details',
|
|
158
|
-
]),
|
|
159
|
-
metadata: {
|
|
160
|
-
stripe_id: line.id,
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
logger.info('stripe invoice items mirrored', { local: item.id, remote: line.id });
|
|
165
|
-
return item;
|
|
166
|
-
})
|
|
167
|
-
);
|
|
168
|
-
}
|
|
181
|
+
const invoice = await ensureStripeInvoice(stripeInvoice, subscription, client);
|
|
169
182
|
|
|
170
183
|
return { subscription, invoice, customer, checkoutSession };
|
|
171
184
|
}
|
|
@@ -186,16 +199,32 @@ export async function handleInvoiceEvent(event: TEventExpanded, client: Stripe)
|
|
|
186
199
|
return;
|
|
187
200
|
}
|
|
188
201
|
|
|
189
|
-
|
|
202
|
+
let localInvoiceId = event.data.object.metadata?.id;
|
|
203
|
+
|
|
204
|
+
// in case we missed some of the events
|
|
205
|
+
const subscriptionId = event.data.object.subscription_details?.metadata?.id;
|
|
206
|
+
const appPid = event.data.object.subscription_details?.metadata?.appPid;
|
|
190
207
|
if (!localInvoiceId) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
208
|
+
if (subscriptionId && appPid && appPid === env.appPid) {
|
|
209
|
+
logger.warn('try mirror invoice from stripe', { invoiceId: event.data.object.id });
|
|
210
|
+
const subscription = await Subscription.findByPk(subscriptionId);
|
|
211
|
+
if (subscription) {
|
|
212
|
+
const method = await PaymentMethod.findByPk(subscription.default_payment_method_id);
|
|
213
|
+
if (method && method.type === 'stripe') {
|
|
214
|
+
const tmp = await ensureStripeInvoice(event.data.object, subscription, method.getStripe());
|
|
215
|
+
localInvoiceId = tmp.id;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
try {
|
|
220
|
+
await waitForStripeInvoiceMirrored(event.data.object.id);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
logger.error('wait for stripe invoice mirror error', { localInvoiceId, error: err });
|
|
223
|
+
}
|
|
196
224
|
|
|
197
|
-
|
|
198
|
-
|
|
225
|
+
logger.warn('local invoice id not found in strip event', { localInvoiceId });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
199
228
|
}
|
|
200
229
|
|
|
201
230
|
const invoice = await Invoice.findByPk(localInvoiceId);
|
|
@@ -232,7 +261,6 @@ export async function handleInvoiceEvent(event: TEventExpanded, client: Stripe)
|
|
|
232
261
|
return;
|
|
233
262
|
}
|
|
234
263
|
|
|
235
|
-
// TODO: handle upcoming invoices?
|
|
236
264
|
if (event.type === 'invoice.upcoming') {
|
|
237
265
|
logger.info('received invoice upcoming event', event);
|
|
238
266
|
}
|
package/api/src/jobs/event.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Op } from 'sequelize';
|
|
|
3
3
|
import logger from '../libs/logger';
|
|
4
4
|
import createQueue from '../libs/queue';
|
|
5
5
|
import { Event } from '../store/models/event';
|
|
6
|
+
import { WebhookAttempt } from '../store/models/webhook-attempt';
|
|
6
7
|
import { WebhookEndpoint } from '../store/models/webhook-endpoint';
|
|
7
8
|
import { getJobId, webhookQueue } from './webhook';
|
|
8
9
|
|
|
@@ -11,34 +12,41 @@ type EventJob = {
|
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export const handleEvent = async (job: EventJob) => {
|
|
14
|
-
logger.info('
|
|
15
|
+
logger.info('handle event', job);
|
|
15
16
|
|
|
16
17
|
const event = await Event.findByPk(job.eventId);
|
|
17
18
|
if (!event) {
|
|
18
|
-
logger.warn(
|
|
19
|
+
logger.warn('event not found', job);
|
|
19
20
|
return;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
if (!event.pending_webhooks) {
|
|
23
|
-
logger.warn(
|
|
24
|
+
logger.warn('event already processed', job);
|
|
24
25
|
return;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
const webhooks = await WebhookEndpoint.findAll({ where: { status: 'enabled', livemode: event.livemode } });
|
|
28
29
|
const eventWebhooks = webhooks.filter((webhook) => webhook.enabled_events.includes(event.type));
|
|
29
30
|
if (eventWebhooks.length === 0) {
|
|
30
|
-
logger.info(
|
|
31
|
+
logger.info('no webhook endpoint for event', job);
|
|
31
32
|
await event.update({ pending_webhooks: 0 });
|
|
32
33
|
return;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
await event.update({ pending_webhooks: eventWebhooks.length });
|
|
36
|
-
eventWebhooks.forEach((webhook) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
id: getJobId(event.id, webhook.id),
|
|
40
|
-
job: { eventId: event.id, webhookId: webhook.id },
|
|
37
|
+
eventWebhooks.forEach(async (webhook) => {
|
|
38
|
+
const attempted = await WebhookAttempt.findOne({
|
|
39
|
+
where: { event_id: event.id, webhook_endpoint_id: webhook.id },
|
|
41
40
|
});
|
|
41
|
+
|
|
42
|
+
// we should only push webhook if it's not attempted before
|
|
43
|
+
if (!attempted) {
|
|
44
|
+
logger.info('schedule initial attempt for event', job);
|
|
45
|
+
webhookQueue.push({
|
|
46
|
+
id: getJobId(event.id, webhook.id),
|
|
47
|
+
job: { eventId: event.id, webhookId: webhook.id },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
42
50
|
});
|
|
43
51
|
};
|
|
44
52
|
|
|
@@ -68,5 +76,5 @@ export const startEventQueue = async () => {
|
|
|
68
76
|
};
|
|
69
77
|
|
|
70
78
|
eventQueue.on('failed', ({ id, job, error }) => {
|
|
71
|
-
logger.error('
|
|
79
|
+
logger.error('event job failed', { id, job, error });
|
|
72
80
|
});
|
package/api/src/jobs/invoice.ts
CHANGED
|
@@ -18,31 +18,31 @@ type InvoiceJob = {
|
|
|
18
18
|
// handle invoice payment
|
|
19
19
|
// TODO: send invoice to user with email
|
|
20
20
|
export const handleInvoice = async (job: InvoiceJob) => {
|
|
21
|
-
logger.info('
|
|
21
|
+
logger.info('handle invoice', job);
|
|
22
22
|
|
|
23
23
|
const invoice = await Invoice.findByPk(job.invoiceId);
|
|
24
24
|
if (!invoice) {
|
|
25
|
-
logger.warn(`
|
|
25
|
+
logger.warn(`invoice not found: ${job.invoiceId}`);
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
if (invoice.status !== 'open') {
|
|
29
|
-
logger.warn(`
|
|
29
|
+
logger.warn(`invoice not open: ${job.invoiceId}`);
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
if (invoice.auto_advance === false) {
|
|
33
|
-
logger.warn(`
|
|
33
|
+
logger.warn(`invoice not configured to auto advance: ${job.invoiceId}`);
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
const supportAutoCharge = await PaymentMethod.supportAutoCharge(invoice.default_payment_method_id);
|
|
38
38
|
if (supportAutoCharge === false) {
|
|
39
|
-
logger.warn(`
|
|
39
|
+
logger.warn(`invoice does not support auto charge: ${job.invoiceId}`);
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// no payment required
|
|
44
44
|
if (invoice.total === '0') {
|
|
45
|
-
logger.warn(`
|
|
45
|
+
logger.warn(`invoice does not require payment: ${job.invoiceId}`);
|
|
46
46
|
|
|
47
47
|
await invoice.update({
|
|
48
48
|
paid: true,
|
|
@@ -59,7 +59,7 @@ export const handleInvoice = async (job: InvoiceJob) => {
|
|
|
59
59
|
const subscription = await Subscription.findByPk(invoice.subscription_id);
|
|
60
60
|
if (subscription && subscription.status === 'incomplete') {
|
|
61
61
|
await subscription.update({ status: subscription.trail_end ? 'trialing' : 'active' });
|
|
62
|
-
logger.info('
|
|
62
|
+
logger.info('invoice subscription updated', subscription.id);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -67,7 +67,7 @@ export const handleInvoice = async (job: InvoiceJob) => {
|
|
|
67
67
|
const checkoutSession = await CheckoutSession.findByPk(invoice.checkout_session_id);
|
|
68
68
|
if (checkoutSession && checkoutSession.status === 'open') {
|
|
69
69
|
await checkoutSession.update({ status: 'complete', payment_status: 'no_payment_required' });
|
|
70
|
-
logger.info('
|
|
70
|
+
logger.info('invoice checkout session updated', checkoutSession.id);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
package/api/src/jobs/payment.ts
CHANGED
|
@@ -19,7 +19,7 @@ type PaymentJob = {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const handlePayment = async (job: PaymentJob) => {
|
|
22
|
-
logger.info('
|
|
22
|
+
logger.info('handle payment', job);
|
|
23
23
|
|
|
24
24
|
const paymentIntent = await PaymentIntent.findByPk(job.paymentIntentId);
|
|
25
25
|
if (!paymentIntent) {
|
|
@@ -21,7 +21,7 @@ type SubscriptionJob = {
|
|
|
21
21
|
|
|
22
22
|
// generate invoice for subscription periodically
|
|
23
23
|
export const handleSubscription = async (job: SubscriptionJob) => {
|
|
24
|
-
logger.info('
|
|
24
|
+
logger.info('handle subscription', job);
|
|
25
25
|
|
|
26
26
|
const subscription = await Subscription.findByPk(job.subscriptionId);
|
|
27
27
|
if (!subscription) {
|
package/api/src/jobs/webhook.ts
CHANGED
|
@@ -20,31 +20,29 @@ export const getJobId = (eventId: string, webhookId: string) => {
|
|
|
20
20
|
|
|
21
21
|
// https://stripe.com/docs/webhooks
|
|
22
22
|
export const handleWebhook = async (job: WebhookJob) => {
|
|
23
|
-
logger.info('
|
|
23
|
+
logger.info('handle webhook', job);
|
|
24
24
|
|
|
25
25
|
const event = await Event.findByPk(job.eventId);
|
|
26
26
|
if (!event) {
|
|
27
|
-
logger.warn(
|
|
27
|
+
logger.warn('event not found when attempt webhook', job);
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const webhook = await WebhookEndpoint.findByPk(job.webhookId);
|
|
32
32
|
if (!webhook) {
|
|
33
|
-
logger.warn(
|
|
33
|
+
logger.warn('webhook not found on attempt', job);
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
if (webhook.status !== 'enabled') {
|
|
37
|
-
logger.warn(
|
|
37
|
+
logger.warn('webhook disabled on attempt', job);
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const
|
|
41
|
+
const lastRetryCount = await WebhookAttempt.max('retry_count', {
|
|
42
42
|
where: { event_id: event.id, webhook_endpoint_id: webhook.id },
|
|
43
|
-
order: [['retry_count', 'DESC']],
|
|
44
|
-
attributes: ['retry_count'],
|
|
45
43
|
});
|
|
46
44
|
|
|
47
|
-
const retryCount =
|
|
45
|
+
const retryCount = lastRetryCount ? +lastRetryCount + 1 : 1;
|
|
48
46
|
|
|
49
47
|
try {
|
|
50
48
|
// verify similar to component call, but supports external urls
|
|
@@ -72,9 +70,9 @@ export const handleWebhook = async (job: WebhookJob) => {
|
|
|
72
70
|
});
|
|
73
71
|
|
|
74
72
|
await event.decrement('pending_webhooks');
|
|
75
|
-
logger.info(
|
|
73
|
+
logger.info('webhook attempt success', { ...job, retryCount });
|
|
76
74
|
} catch (err: any) {
|
|
77
|
-
logger.
|
|
75
|
+
logger.warn('webhook attempt error', { ...job, retryCount, message: err.message });
|
|
78
76
|
await WebhookAttempt.create({
|
|
79
77
|
livemode: event.livemode,
|
|
80
78
|
event_id: event.id,
|
|
@@ -91,7 +89,7 @@ export const handleWebhook = async (job: WebhookJob) => {
|
|
|
91
89
|
webhookQueue.push({
|
|
92
90
|
id: getJobId(event.id, webhook.id),
|
|
93
91
|
job: { eventId: event.id, webhookId: webhook.id },
|
|
94
|
-
runAt: getNextRetry(retryCount
|
|
92
|
+
runAt: getNextRetry(retryCount),
|
|
95
93
|
});
|
|
96
94
|
});
|
|
97
95
|
}
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.23",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"devDependencies": {
|
|
101
101
|
"@arcblock/eslint-config": "^0.2.4",
|
|
102
102
|
"@arcblock/eslint-config-ts": "^0.2.4",
|
|
103
|
-
"@did-pay/types": "1.13.
|
|
103
|
+
"@did-pay/types": "1.13.23",
|
|
104
104
|
"@types/cookie-parser": "^1.4.4",
|
|
105
105
|
"@types/cors": "^2.8.14",
|
|
106
106
|
"@types/dotenv-flow": "^3.3.1",
|
|
@@ -137,5 +137,5 @@
|
|
|
137
137
|
"parser": "typescript"
|
|
138
138
|
}
|
|
139
139
|
},
|
|
140
|
-
"gitHead": "
|
|
140
|
+
"gitHead": "a4ba80b4e2d020c912361af6fd555f8bfc52f54f"
|
|
141
141
|
}
|
|
@@ -4,6 +4,8 @@ import { Button, IconButton, ListItemText, Menu, MenuItem } from '@mui/material'
|
|
|
4
4
|
import React, { useState } from 'react';
|
|
5
5
|
import type { LiteralUnion } from 'type-fest';
|
|
6
6
|
|
|
7
|
+
import { stopEvent } from '../libs/util';
|
|
8
|
+
|
|
7
9
|
type ActionItem = {
|
|
8
10
|
label: string;
|
|
9
11
|
handler: Function;
|
|
@@ -30,20 +32,12 @@ export default function Actions(props: ActionsProps) {
|
|
|
30
32
|
const open = Boolean(anchorEl);
|
|
31
33
|
|
|
32
34
|
const onOpen = (e: React.SyntheticEvent<any>) => {
|
|
33
|
-
|
|
34
|
-
e.stopPropagation();
|
|
35
|
-
e.preventDefault();
|
|
36
|
-
// eslint-disable-next-line no-empty
|
|
37
|
-
} catch {}
|
|
35
|
+
stopEvent(e);
|
|
38
36
|
setAnchorEl(e.currentTarget);
|
|
39
37
|
};
|
|
40
38
|
|
|
41
39
|
const onClose = (e: React.SyntheticEvent<any>, handler?: Function) => {
|
|
42
|
-
|
|
43
|
-
e.stopPropagation();
|
|
44
|
-
e.preventDefault();
|
|
45
|
-
// eslint-disable-next-line no-empty
|
|
46
|
-
} catch {}
|
|
40
|
+
stopEvent(e);
|
|
47
41
|
setAnchorEl(null);
|
|
48
42
|
|
|
49
43
|
if (typeof handler === 'function') {
|
|
@@ -1,27 +1,48 @@
|
|
|
1
|
-
import type { TPaymentMethod } from '@did-pay/types';
|
|
1
|
+
import type { PaymentDetails, TPaymentMethod } from '@did-pay/types';
|
|
2
2
|
import { OpenInNewOutlined } from '@mui/icons-material';
|
|
3
3
|
import { Link, Stack, Typography } from '@mui/material';
|
|
4
4
|
import { joinURL } from 'ufo';
|
|
5
5
|
|
|
6
|
-
const getTxLink = (method: TPaymentMethod,
|
|
6
|
+
const getTxLink = (method: TPaymentMethod, details: PaymentDetails) => {
|
|
7
7
|
if (method.type === 'arcblock') {
|
|
8
|
-
return
|
|
8
|
+
return {
|
|
9
|
+
link: joinURL(method.settings.arcblock?.explorer_host as string, '/tx', details.arcblock?.tx_hash as string),
|
|
10
|
+
text: details.arcblock?.tx_hash as string,
|
|
11
|
+
};
|
|
9
12
|
}
|
|
10
13
|
if (method.type === 'bitcoin') {
|
|
11
|
-
return
|
|
14
|
+
return {
|
|
15
|
+
link: joinURL(method.settings.bitcoin?.explorer_host as string, '/tx', details.bitcoin?.tx_hash as string),
|
|
16
|
+
text: details.bitcoin?.tx_hash as string,
|
|
17
|
+
};
|
|
12
18
|
}
|
|
13
19
|
if (method.type === 'ethereum') {
|
|
14
|
-
return
|
|
20
|
+
return {
|
|
21
|
+
link: joinURL(method.settings.ethereum?.explorer_host as string, '/tx', details.ethereum?.tx_hash as string),
|
|
22
|
+
text: details.ethereum?.tx_hash as string,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (method.type === 'stripe') {
|
|
26
|
+
const dashboard = method.livemode ? 'https://dashboard.stripe.com' : 'https://dashboard.stripe.com/test';
|
|
27
|
+
return {
|
|
28
|
+
link: joinURL(
|
|
29
|
+
method.settings.stripe?.dashboard || dashboard,
|
|
30
|
+
'payments',
|
|
31
|
+
details.stripe?.payment_intent_id as string
|
|
32
|
+
),
|
|
33
|
+
text: details.stripe?.payment_intent_id as string,
|
|
34
|
+
};
|
|
15
35
|
}
|
|
16
36
|
|
|
17
|
-
return
|
|
37
|
+
return { text: 'N/A', link: '' };
|
|
18
38
|
};
|
|
19
39
|
|
|
20
|
-
export default function TxLink(props: {
|
|
40
|
+
export default function TxLink(props: { details: PaymentDetails; method: TPaymentMethod }) {
|
|
41
|
+
const { text, link } = getTxLink(props.method, props.details);
|
|
21
42
|
return (
|
|
22
|
-
<Link href={
|
|
43
|
+
<Link href={link} target="_blank" rel="noopener noreferrer">
|
|
23
44
|
<Stack component="span" direction="row" alignItems="center" spacing={1}>
|
|
24
|
-
<Typography component="span">{
|
|
45
|
+
<Typography component="span">{text}</Typography>
|
|
25
46
|
<OpenInNewOutlined fontSize="small" />
|
|
26
47
|
</Stack>
|
|
27
48
|
</Link>
|
|
@@ -17,29 +17,13 @@ export default function ConfirmDialog({
|
|
|
17
17
|
loading?: boolean;
|
|
18
18
|
}) {
|
|
19
19
|
const { t } = useLocaleContext();
|
|
20
|
-
const handleConfirm = (e: any) => {
|
|
21
|
-
try {
|
|
22
|
-
e.stopPropagation();
|
|
23
|
-
e.preventDefault();
|
|
24
|
-
// eslint-disable-next-line no-empty
|
|
25
|
-
} catch {}
|
|
26
|
-
onConfirm(e);
|
|
27
|
-
};
|
|
28
|
-
const handleCancel = (e: any) => {
|
|
29
|
-
try {
|
|
30
|
-
e.stopPropagation();
|
|
31
|
-
e.preventDefault();
|
|
32
|
-
// eslint-disable-next-line no-empty
|
|
33
|
-
} catch {}
|
|
34
|
-
onCancel(e);
|
|
35
|
-
};
|
|
36
20
|
|
|
37
21
|
return (
|
|
38
22
|
<Confirm
|
|
39
23
|
open
|
|
40
24
|
title={title}
|
|
41
|
-
onConfirm={
|
|
42
|
-
onCancel={
|
|
25
|
+
onConfirm={onConfirm}
|
|
26
|
+
onCancel={onCancel}
|
|
43
27
|
confirmButton={{
|
|
44
28
|
text: t('common.confirm'),
|
|
45
29
|
props: { color: 'error', size: 'small', variant: 'contained', disabled: !!loading },
|
|
@@ -7,6 +7,7 @@ import type { LiteralUnion } from 'type-fest';
|
|
|
7
7
|
import api from '../../libs/api';
|
|
8
8
|
import { formatError } from '../../libs/util';
|
|
9
9
|
import Actions from '../actions';
|
|
10
|
+
import ClickBoundary from '../click-boundary';
|
|
10
11
|
import ConfirmDialog from '../confirm';
|
|
11
12
|
|
|
12
13
|
type Props = {
|
|
@@ -41,7 +42,7 @@ export default function CustomerActions({ data, onChange, variant }: Props) {
|
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
return (
|
|
44
|
-
|
|
45
|
+
<ClickBoundary>
|
|
45
46
|
<Actions
|
|
46
47
|
variant={variant}
|
|
47
48
|
actions={[
|
|
@@ -68,6 +69,6 @@ export default function CustomerActions({ data, onChange, variant }: Props) {
|
|
|
68
69
|
loading={state.loading}
|
|
69
70
|
/>
|
|
70
71
|
)}
|
|
71
|
-
|
|
72
|
+
</ClickBoundary>
|
|
72
73
|
);
|
|
73
74
|
}
|
|
@@ -8,6 +8,7 @@ import type { LiteralUnion } from 'type-fest';
|
|
|
8
8
|
import api from '../../libs/api';
|
|
9
9
|
import { formatError } from '../../libs/util';
|
|
10
10
|
import Actions from '../actions';
|
|
11
|
+
import ClickBoundary from '../click-boundary';
|
|
11
12
|
import ConfirmDialog from '../confirm';
|
|
12
13
|
|
|
13
14
|
type Props = {
|
|
@@ -78,7 +79,7 @@ export default function InvoiceActions({ data, variant, onChange }: Props) {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
return (
|
|
81
|
-
|
|
82
|
+
<ClickBoundary>
|
|
82
83
|
<Actions variant={variant} actions={actions} />
|
|
83
84
|
{state.action === 'xxx' && (
|
|
84
85
|
<ConfirmDialog
|
|
@@ -89,6 +90,6 @@ export default function InvoiceActions({ data, variant, onChange }: Props) {
|
|
|
89
90
|
loading={state.loading}
|
|
90
91
|
/>
|
|
91
92
|
)}
|
|
92
|
-
|
|
93
|
+
</ClickBoundary>
|
|
93
94
|
);
|
|
94
95
|
}
|
|
@@ -8,6 +8,7 @@ import type { LiteralUnion } from 'type-fest';
|
|
|
8
8
|
import api from '../../libs/api';
|
|
9
9
|
import { formatError } from '../../libs/util';
|
|
10
10
|
import Actions from '../actions';
|
|
11
|
+
import ClickBoundary from '../click-boundary';
|
|
11
12
|
import ConfirmDialog from '../confirm';
|
|
12
13
|
|
|
13
14
|
type Props = {
|
|
@@ -58,14 +59,14 @@ export default function PaymentIntentActions({ data, variant }: Props) {
|
|
|
58
59
|
if (variant === 'compact') {
|
|
59
60
|
actions.push({
|
|
60
61
|
label: t('admin.paymentIntent.view'),
|
|
61
|
-
handler: () => navigate(`/admin/payments/${data.
|
|
62
|
+
handler: () => navigate(`/admin/payments/${data.id}`),
|
|
62
63
|
color: 'primary',
|
|
63
64
|
disabled: false,
|
|
64
65
|
});
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
return (
|
|
68
|
-
|
|
69
|
+
<ClickBoundary>
|
|
69
70
|
<Actions variant={variant} actions={actions} />
|
|
70
71
|
{state.action === 'refund' && (
|
|
71
72
|
<ConfirmDialog
|
|
@@ -76,6 +77,6 @@ export default function PaymentIntentActions({ data, variant }: Props) {
|
|
|
76
77
|
loading={state.loading}
|
|
77
78
|
/>
|
|
78
79
|
)}
|
|
79
|
-
|
|
80
|
+
</ClickBoundary>
|
|
80
81
|
);
|
|
81
82
|
}
|
|
@@ -6,7 +6,7 @@ import { Alert, CircularProgress, ToggleButton, ToggleButtonGroup, Typography }
|
|
|
6
6
|
import { fromUnitToToken } from '@ocap/util';
|
|
7
7
|
import { useRequest } from 'ahooks';
|
|
8
8
|
import { useEffect, useState } from 'react';
|
|
9
|
-
import {
|
|
9
|
+
import { useNavigate } from 'react-router-dom';
|
|
10
10
|
|
|
11
11
|
import api from '../../libs/api';
|
|
12
12
|
import { formatTime, getPaymentIntentStatusColor } from '../../libs/util';
|
|
@@ -152,7 +152,7 @@ export default function PaymentList({ customer_id, invoice_id, features }: ListP
|
|
|
152
152
|
options: {
|
|
153
153
|
customBodyRenderLite: (_: string, index: number) => {
|
|
154
154
|
const item = data.list[index] as TPaymentIntentExpanded;
|
|
155
|
-
return
|
|
155
|
+
return item.customer.email;
|
|
156
156
|
},
|
|
157
157
|
},
|
|
158
158
|
});
|
|
@@ -7,6 +7,7 @@ import type { LiteralUnion } from 'type-fest';
|
|
|
7
7
|
import api from '../../libs/api';
|
|
8
8
|
import { formatError } from '../../libs/util';
|
|
9
9
|
import Actions from '../actions';
|
|
10
|
+
import ClickBoundary from '../click-boundary';
|
|
10
11
|
import ConfirmDialog from '../confirm';
|
|
11
12
|
import RenamePaymentLink from './rename';
|
|
12
13
|
|
|
@@ -68,7 +69,7 @@ export default function PaymentLinkActions({ data, variant, onChange }: Props) {
|
|
|
68
69
|
};
|
|
69
70
|
|
|
70
71
|
return (
|
|
71
|
-
|
|
72
|
+
<ClickBoundary>
|
|
72
73
|
<Actions
|
|
73
74
|
variant={variant}
|
|
74
75
|
actions={[
|
|
@@ -109,6 +110,6 @@ export default function PaymentLinkActions({ data, variant, onChange }: Props) {
|
|
|
109
110
|
loading={state.loading}
|
|
110
111
|
/>
|
|
111
112
|
)}
|
|
112
|
-
|
|
113
|
+
</ClickBoundary>
|
|
113
114
|
);
|
|
114
115
|
}
|
|
@@ -9,6 +9,7 @@ import { useSettingsContext } from '../../contexts/settings';
|
|
|
9
9
|
import api from '../../libs/api';
|
|
10
10
|
import { formatError, formatPrice } from '../../libs/util';
|
|
11
11
|
import Actions from '../actions';
|
|
12
|
+
import ClickBoundary from '../click-boundary';
|
|
12
13
|
import InfoCard from '../info-card';
|
|
13
14
|
import EditProduct from '../product/edit';
|
|
14
15
|
|
|
@@ -52,21 +53,23 @@ export default function LineItem({ prefix, product, valid, onUpdate, onRemove }:
|
|
|
52
53
|
borderRadius: 2,
|
|
53
54
|
position: 'relative',
|
|
54
55
|
}}>
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
56
|
+
<ClickBoundary>
|
|
57
|
+
<Actions
|
|
58
|
+
sx={{ position: 'absolute', top: 0, right: 0 }}
|
|
59
|
+
actions={[
|
|
60
|
+
{
|
|
61
|
+
label: t('admin.product.edit'),
|
|
62
|
+
handler: () => setState({ editing: true }),
|
|
63
|
+
color: 'primary',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
label: t('admin.product.remove'),
|
|
67
|
+
handler: onRemove,
|
|
68
|
+
color: 'error',
|
|
69
|
+
},
|
|
70
|
+
]}
|
|
71
|
+
/>
|
|
72
|
+
</ClickBoundary>
|
|
70
73
|
<Stack direction="column" alignItems="flex-start">
|
|
71
74
|
<InfoCard
|
|
72
75
|
logo={product.images[0]}
|
|
@@ -9,7 +9,6 @@ export default function StripeMethodForm() {
|
|
|
9
9
|
return (
|
|
10
10
|
<>
|
|
11
11
|
<FormInput
|
|
12
|
-
key="name"
|
|
13
12
|
name="name"
|
|
14
13
|
type="text"
|
|
15
14
|
rules={{ required: true }}
|
|
@@ -17,7 +16,6 @@ export default function StripeMethodForm() {
|
|
|
17
16
|
placeholder={t('admin.paymentMethod.name.tip')}
|
|
18
17
|
/>
|
|
19
18
|
<FormInput
|
|
20
|
-
key="description"
|
|
21
19
|
name="description"
|
|
22
20
|
type="text"
|
|
23
21
|
rules={{ required: true }}
|
|
@@ -25,7 +23,13 @@ export default function StripeMethodForm() {
|
|
|
25
23
|
placeholder={t('admin.paymentMethod.description.tip')}
|
|
26
24
|
/>
|
|
27
25
|
<FormInput
|
|
28
|
-
|
|
26
|
+
name="settings.stripe.dashboard"
|
|
27
|
+
type="text"
|
|
28
|
+
rules={{ required: true }}
|
|
29
|
+
label={t('admin.paymentMethod.stripe.dashboard.label')}
|
|
30
|
+
placeholder={t('admin.paymentMethod.stripe.dashboard.tip')}
|
|
31
|
+
/>
|
|
32
|
+
<FormInput
|
|
29
33
|
name="settings.stripe.publishable_key"
|
|
30
34
|
type="text"
|
|
31
35
|
rules={{ required: true }}
|
|
@@ -33,7 +37,6 @@ export default function StripeMethodForm() {
|
|
|
33
37
|
placeholder={t('admin.paymentMethod.stripe.publishable_key.tip')}
|
|
34
38
|
/>
|
|
35
39
|
<FormInput
|
|
36
|
-
key="secret_key"
|
|
37
40
|
name="settings.stripe.secret_key"
|
|
38
41
|
type="password"
|
|
39
42
|
rules={{ required: true }}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
2
|
|
|
3
3
|
import Actions from '../actions';
|
|
4
|
+
import ClickBoundary from '../click-boundary';
|
|
4
5
|
|
|
5
6
|
type PriceActionProps = {
|
|
6
7
|
onDuplicate: Function;
|
|
@@ -11,11 +12,13 @@ export default function PriceActions(props: PriceActionProps) {
|
|
|
11
12
|
const { t } = useLocaleContext();
|
|
12
13
|
|
|
13
14
|
return (
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
<ClickBoundary>
|
|
16
|
+
<Actions
|
|
17
|
+
actions={[
|
|
18
|
+
{ label: t('admin.price.duplicate'), handler: props.onDuplicate, color: 'primary' },
|
|
19
|
+
{ label: t('admin.price.remove'), handler: props.onRemove, color: 'error' },
|
|
20
|
+
]}
|
|
21
|
+
/>
|
|
22
|
+
</ClickBoundary>
|
|
20
23
|
);
|
|
21
24
|
}
|
|
@@ -7,6 +7,7 @@ import type { LiteralUnion } from 'type-fest';
|
|
|
7
7
|
import api from '../../libs/api';
|
|
8
8
|
import { formatError } from '../../libs/util';
|
|
9
9
|
import Actions from '../actions';
|
|
10
|
+
import ClickBoundary from '../click-boundary';
|
|
10
11
|
import ConfirmDialog from '../confirm';
|
|
11
12
|
import EditProduct from './edit';
|
|
12
13
|
|
|
@@ -68,7 +69,7 @@ export default function ProductActions({ data, variant, onChange }: ProductActio
|
|
|
68
69
|
};
|
|
69
70
|
|
|
70
71
|
return (
|
|
71
|
-
|
|
72
|
+
<ClickBoundary>
|
|
72
73
|
<Actions
|
|
73
74
|
variant={variant}
|
|
74
75
|
actions={[
|
|
@@ -120,6 +121,6 @@ export default function ProductActions({ data, variant, onChange }: ProductActio
|
|
|
120
121
|
loading={state.loading}
|
|
121
122
|
/>
|
|
122
123
|
)}
|
|
123
|
-
|
|
124
|
+
</ClickBoundary>
|
|
124
125
|
);
|
|
125
126
|
}
|
|
@@ -9,6 +9,7 @@ import type { LiteralUnion } from 'type-fest';
|
|
|
9
9
|
import api from '../../../libs/api';
|
|
10
10
|
import { formatError } from '../../../libs/util';
|
|
11
11
|
import Actions from '../../actions';
|
|
12
|
+
import ClickBoundary from '../../click-boundary';
|
|
12
13
|
import ConfirmDialog from '../../confirm';
|
|
13
14
|
import SubscriptionCancelForm from './cancel';
|
|
14
15
|
import SubscriptionPauseForm from './pause';
|
|
@@ -111,7 +112,7 @@ function SubscriptionActionsInner({ data, variant, onChange }: Props) {
|
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
return (
|
|
114
|
-
|
|
115
|
+
<ClickBoundary>
|
|
115
116
|
<Actions variant={variant} actions={actions} />
|
|
116
117
|
{state.action === 'cancel' && (
|
|
117
118
|
<ConfirmDialog
|
|
@@ -140,7 +141,7 @@ function SubscriptionActionsInner({ data, variant, onChange }: Props) {
|
|
|
140
141
|
loading={state.loading}
|
|
141
142
|
/>
|
|
142
143
|
)}
|
|
143
|
-
|
|
144
|
+
</ClickBoundary>
|
|
144
145
|
);
|
|
145
146
|
}
|
|
146
147
|
|
|
@@ -3,6 +3,7 @@ import type { TLineItemExpanded } from '@did-pay/types';
|
|
|
3
3
|
import { useNavigate } from 'react-router-dom';
|
|
4
4
|
|
|
5
5
|
import Actions from '../../actions';
|
|
6
|
+
import ClickBoundary from '../../click-boundary';
|
|
6
7
|
|
|
7
8
|
type Props = {
|
|
8
9
|
data: TLineItemExpanded;
|
|
@@ -13,19 +14,21 @@ export default function LineItemActions(props: Props) {
|
|
|
13
14
|
const navigate = useNavigate();
|
|
14
15
|
|
|
15
16
|
return (
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
17
|
+
<ClickBoundary>
|
|
18
|
+
<Actions
|
|
19
|
+
actions={[
|
|
20
|
+
{
|
|
21
|
+
label: t('admin.price.view'),
|
|
22
|
+
handler: () => navigate(`/admin/products/${props.data.price_id}`),
|
|
23
|
+
color: 'primary',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
label: t('admin.product.view'),
|
|
27
|
+
handler: () => navigate(`/admin/products/${props.data.price.product_id}`),
|
|
28
|
+
color: 'primary',
|
|
29
|
+
},
|
|
30
|
+
]}
|
|
31
|
+
/>
|
|
32
|
+
</ClickBoundary>
|
|
30
33
|
);
|
|
31
34
|
}
|
package/src/libs/util.ts
CHANGED
|
@@ -572,3 +572,13 @@ export function getSupportedPaymentCurrencies(items: TLineItemExpanded[]) {
|
|
|
572
572
|
export function isValidCountry(code: string) {
|
|
573
573
|
return defaultCountries.some((x) => x[1] === code);
|
|
574
574
|
}
|
|
575
|
+
|
|
576
|
+
export function stopEvent(e: React.SyntheticEvent<any>) {
|
|
577
|
+
try {
|
|
578
|
+
e.stopPropagation();
|
|
579
|
+
e.preventDefault();
|
|
580
|
+
// eslint-disable-next-line no-empty
|
|
581
|
+
} catch {
|
|
582
|
+
// Do nothing
|
|
583
|
+
}
|
|
584
|
+
}
|
package/src/locales/en.tsx
CHANGED
|
@@ -232,6 +232,10 @@ export default flat({
|
|
|
232
232
|
tip: 'Not consumer facing',
|
|
233
233
|
},
|
|
234
234
|
stripe: {
|
|
235
|
+
dashboard: {
|
|
236
|
+
label: 'Dashboard URL',
|
|
237
|
+
tip: 'Used to generate links to Stripe dashboard',
|
|
238
|
+
},
|
|
235
239
|
publishable_key: {
|
|
236
240
|
label: 'Publishable Key',
|
|
237
241
|
tip: 'Publishable Key, See Dashboard > Developers > API Keys',
|
|
@@ -176,7 +176,7 @@ export default function SubscriptionDetail(props: { id: string }) {
|
|
|
176
176
|
{data.payment_details?.arcblock?.tx_hash && (
|
|
177
177
|
<InfoRow
|
|
178
178
|
label={t('common.txHash')}
|
|
179
|
-
value={<TxLink
|
|
179
|
+
value={<TxLink details={data.payment_details} method={data.paymentMethod} />}
|
|
180
180
|
/>
|
|
181
181
|
)}
|
|
182
182
|
</Stack>
|
|
@@ -141,12 +141,7 @@ export default function PaymentIntentDetail(props: { id: string }) {
|
|
|
141
141
|
/>
|
|
142
142
|
<InfoRow
|
|
143
143
|
label={t('common.txHash')}
|
|
144
|
-
value={
|
|
145
|
-
<TxLink
|
|
146
|
-
hash={data.payment_details?.arcblock?.tx_hash || data.metadata?.txHash}
|
|
147
|
-
method={data.paymentMethod}
|
|
148
|
-
/>
|
|
149
|
-
}
|
|
144
|
+
value={<TxLink details={data.payment_details as any} method={data.paymentMethod} />}
|
|
150
145
|
/>
|
|
151
146
|
</Stack>
|
|
152
147
|
</Box>
|
|
@@ -9,11 +9,13 @@ import { dispatch } from 'use-bus';
|
|
|
9
9
|
|
|
10
10
|
import DrawerForm from '../../../../components/drawer-form';
|
|
11
11
|
import PaymentMethodForm from '../../../../components/payment-method/form';
|
|
12
|
+
import { useSettingsContext } from '../../../../contexts/settings';
|
|
12
13
|
import api from '../../../../libs/api';
|
|
13
14
|
import { formatError } from '../../../../libs/util';
|
|
14
15
|
|
|
15
16
|
export default function PaymentMethodCreate() {
|
|
16
17
|
const { t } = useLocaleContext();
|
|
18
|
+
const settings = useSettingsContext();
|
|
17
19
|
|
|
18
20
|
const methods = useForm<TPaymentMethod>({
|
|
19
21
|
defaultValues: {
|
|
@@ -27,6 +29,7 @@ export default function PaymentMethodCreate() {
|
|
|
27
29
|
explorer_host: '',
|
|
28
30
|
},
|
|
29
31
|
stripe: {
|
|
32
|
+
dashboard: settings.livemode ? 'https://dashboard.stripe.com' : 'https://dashboard.stripe.com/test',
|
|
30
33
|
publishable_key: '',
|
|
31
34
|
secret_key: '',
|
|
32
35
|
},
|