payment-kit 1.13.43 → 1.13.45
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 +1 -1
- package/api/src/integrations/stripe/handlers/payment-intent.ts +9 -25
- package/api/src/jobs/payment.ts +76 -48
- package/api/src/routes/connect/collect.ts +2 -12
- package/api/src/routes/connect/pay.ts +3 -24
- package/api/src/routes/connect/subscribe.ts +0 -1
- package/api/src/routes/pricing-table.ts +3 -0
- package/blocklet.yml +1 -1
- package/package.json +3 -3
- package/src/components/checkout/form/index.tsx +8 -0
|
@@ -176,7 +176,7 @@ export async function ensureStripeInvoice(stripeInvoice: any, subscription: Subs
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
export async function handleStripeInvoiceCreated(event: TEventExpanded, client: Stripe) {
|
|
179
|
-
if (['invoice.created', 'payment_intent.created'].includes(event.type) === false) {
|
|
179
|
+
if (['invoice.created', 'payment_intent.created', 'payment_intent.succeeded'].includes(event.type) === false) {
|
|
180
180
|
logger.warn('abort because event type not expected', { id: event.id, type: event.type });
|
|
181
181
|
return null;
|
|
182
182
|
}
|
|
@@ -4,9 +4,10 @@ import pick from 'lodash/pick';
|
|
|
4
4
|
import pWaitFor from 'p-wait-for';
|
|
5
5
|
import type Stripe from 'stripe';
|
|
6
6
|
|
|
7
|
+
import { handlePaymentSucceed } from '../../../jobs/payment';
|
|
7
8
|
import dayjs from '../../../libs/dayjs';
|
|
8
9
|
import logger from '../../../libs/logger';
|
|
9
|
-
import {
|
|
10
|
+
import { Invoice, PaymentIntent, PaymentMethod, TEventExpanded } from '../../../store/models';
|
|
10
11
|
import { handleStripeInvoiceCreated } from './invoice';
|
|
11
12
|
|
|
12
13
|
export async function handleStripePaymentSucceed(paymentIntent: PaymentIntent, event?: TEventExpanded) {
|
|
@@ -20,30 +21,7 @@ export async function handleStripePaymentSucceed(paymentIntent: PaymentIntent, e
|
|
|
20
21
|
});
|
|
21
22
|
logger.info('payment intent succeeded on stripe event', { locale: paymentIntent.id });
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
if (checkoutSession) {
|
|
25
|
-
await checkoutSession.update({
|
|
26
|
-
status: 'complete',
|
|
27
|
-
payment_status: 'paid',
|
|
28
|
-
payment_details: paymentIntent.payment_details,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (paymentIntent.invoice_id) {
|
|
33
|
-
const invoice = await Invoice.findByPk(paymentIntent.invoice_id);
|
|
34
|
-
if (invoice && invoice.status !== 'paid') {
|
|
35
|
-
await invoice.update({
|
|
36
|
-
paid: true,
|
|
37
|
-
status: 'paid',
|
|
38
|
-
amount_due: '0',
|
|
39
|
-
amount_paid: paymentIntent.amount,
|
|
40
|
-
amount_remaining: '0',
|
|
41
|
-
attempt_count: invoice.attempt_count + 1,
|
|
42
|
-
attempted: true,
|
|
43
|
-
status_transitions: { ...invoice.status_transitions, paid_at: dayjs().unix() },
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}
|
|
24
|
+
await handlePaymentSucceed(paymentIntent);
|
|
47
25
|
}
|
|
48
26
|
|
|
49
27
|
export async function syncStripPayment(paymentIntent: PaymentIntent) {
|
|
@@ -59,6 +37,8 @@ export async function syncStripPayment(paymentIntent: PaymentIntent) {
|
|
|
59
37
|
const client = await method.getStripeClient();
|
|
60
38
|
const stripeIntent = await client.paymentIntents.retrieve(paymentIntent.metadata.stripe_id);
|
|
61
39
|
if (stripeIntent) {
|
|
40
|
+
const justSucceed = stripeIntent.status === 'succeeded' && paymentIntent.status !== 'succeeded';
|
|
41
|
+
|
|
62
42
|
// @ts-ignore
|
|
63
43
|
await paymentIntent.update({
|
|
64
44
|
amount: String(stripeIntent.amount),
|
|
@@ -68,6 +48,10 @@ export async function syncStripPayment(paymentIntent: PaymentIntent) {
|
|
|
68
48
|
...pick(stripeIntent, ['status', 'confirmation_method', 'capture_method', 'last_payment_error']),
|
|
69
49
|
});
|
|
70
50
|
logger.info('stripe payment intent synced', { locale: paymentIntent.id, remote: stripeIntent.id });
|
|
51
|
+
|
|
52
|
+
if (justSucceed) {
|
|
53
|
+
await handlePaymentSucceed(paymentIntent);
|
|
54
|
+
}
|
|
71
55
|
}
|
|
72
56
|
}
|
|
73
57
|
|
package/api/src/jobs/payment.ts
CHANGED
|
@@ -19,6 +19,62 @@ type PaymentJob = {
|
|
|
19
19
|
retryOnError?: boolean;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
export const handlePaymentSucceed = async (paymentIntent: PaymentIntent) => {
|
|
23
|
+
let invoice;
|
|
24
|
+
if (paymentIntent.invoice_id) {
|
|
25
|
+
invoice = await Invoice.findByPk(paymentIntent.invoice_id);
|
|
26
|
+
}
|
|
27
|
+
if (!invoice) {
|
|
28
|
+
const checkoutSession = await CheckoutSession.findOne({ where: { payment_intent_id: paymentIntent.id } });
|
|
29
|
+
if (checkoutSession && checkoutSession.status === 'open') {
|
|
30
|
+
await checkoutSession.update({
|
|
31
|
+
status: 'complete',
|
|
32
|
+
payment_status: 'paid',
|
|
33
|
+
payment_details: paymentIntent.payment_details,
|
|
34
|
+
});
|
|
35
|
+
logger.info(`CheckoutSession ${checkoutSession.id} updated on payment done ${paymentIntent.id}`);
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await invoice.update({
|
|
41
|
+
paid: true,
|
|
42
|
+
status: 'paid',
|
|
43
|
+
amount_due: '0',
|
|
44
|
+
amount_paid: paymentIntent.amount,
|
|
45
|
+
amount_remaining: '0',
|
|
46
|
+
attempt_count: invoice.attempt_count + 1,
|
|
47
|
+
attempted: true,
|
|
48
|
+
status_transitions: { ...invoice.status_transitions, paid_at: dayjs().unix() },
|
|
49
|
+
});
|
|
50
|
+
logger.info(`Invoice ${invoice.id} updated on payment done: ${paymentIntent.id}`);
|
|
51
|
+
|
|
52
|
+
if (invoice.subscription_id) {
|
|
53
|
+
const subscription = await Subscription.findByPk(invoice.subscription_id);
|
|
54
|
+
if (subscription) {
|
|
55
|
+
if (subscription.status === 'incomplete') {
|
|
56
|
+
await subscription.update({ status: subscription.trail_end ? 'trialing' : 'active' });
|
|
57
|
+
logger.info(`Subscription ${subscription.id} updated on payment done ${invoice.id}`);
|
|
58
|
+
} else {
|
|
59
|
+
await subscription.update({ status: 'active' });
|
|
60
|
+
logger.info(`Subscription ${subscription.id} moved to active after payment done ${paymentIntent.id}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (invoice.checkout_session_id) {
|
|
66
|
+
const checkoutSession = await CheckoutSession.findByPk(invoice.checkout_session_id);
|
|
67
|
+
if (checkoutSession && checkoutSession.status === 'open') {
|
|
68
|
+
await checkoutSession.update({
|
|
69
|
+
status: 'complete',
|
|
70
|
+
payment_status: 'paid',
|
|
71
|
+
payment_details: paymentIntent.payment_details,
|
|
72
|
+
});
|
|
73
|
+
logger.info(`CheckoutSession ${checkoutSession.id} updated on payment done ${paymentIntent.id}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
22
78
|
export const handlePayment = async (job: PaymentJob) => {
|
|
23
79
|
logger.info('handle payment', job);
|
|
24
80
|
|
|
@@ -110,49 +166,7 @@ export const handlePayment = async (job: PaymentJob) => {
|
|
|
110
166
|
},
|
|
111
167
|
});
|
|
112
168
|
|
|
113
|
-
|
|
114
|
-
await invoice.update({
|
|
115
|
-
paid: true,
|
|
116
|
-
status: 'paid',
|
|
117
|
-
amount_due: '0',
|
|
118
|
-
amount_paid: paymentIntent.amount,
|
|
119
|
-
amount_remaining: '0',
|
|
120
|
-
attempt_count: invoice.attempt_count + 1,
|
|
121
|
-
attempted: true,
|
|
122
|
-
status_transitions: { ...invoice.status_transitions, paid_at: dayjs().unix() },
|
|
123
|
-
});
|
|
124
|
-
logger.info(`Invoice ${invoice.id} updated on payment done: ${job.paymentIntentId}`);
|
|
125
|
-
|
|
126
|
-
if (invoice.subscription_id) {
|
|
127
|
-
const subscription = await Subscription.findByPk(invoice.subscription_id);
|
|
128
|
-
if (subscription) {
|
|
129
|
-
if (subscription.status === 'incomplete') {
|
|
130
|
-
await subscription.update({ status: subscription.trail_end ? 'trialing' : 'active' });
|
|
131
|
-
logger.info(`Subscription ${subscription.id} updated on payment done ${invoice.id}`);
|
|
132
|
-
} else {
|
|
133
|
-
await subscription.update({ status: 'active' });
|
|
134
|
-
logger.info(`Subscription ${subscription.id} moved to active after payment done ${invoice.id}`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (invoice.checkout_session_id) {
|
|
140
|
-
const checkoutSession = await CheckoutSession.findByPk(invoice.checkout_session_id);
|
|
141
|
-
if (checkoutSession && checkoutSession.status === 'open') {
|
|
142
|
-
await checkoutSession.update({
|
|
143
|
-
status: 'complete',
|
|
144
|
-
payment_status: 'paid',
|
|
145
|
-
payment_details: {
|
|
146
|
-
arcblock: {
|
|
147
|
-
tx_hash: txHash,
|
|
148
|
-
payer: payer as string,
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
logger.info(`CheckoutSession ${checkoutSession.id} updated on payment done ${invoice.id}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
169
|
+
await handlePaymentSucceed(paymentIntent);
|
|
156
170
|
} catch (err) {
|
|
157
171
|
logger.error('PaymentIntent capture failed', { error: err, id: paymentIntent.id });
|
|
158
172
|
|
|
@@ -165,10 +179,27 @@ export const handlePayment = async (job: PaymentJob) => {
|
|
|
165
179
|
payment_method_type: paymentMethod.type,
|
|
166
180
|
};
|
|
167
181
|
|
|
168
|
-
if (
|
|
182
|
+
if (!job.retryOnError) {
|
|
183
|
+
// To a final state without any retry
|
|
184
|
+
await paymentIntent.update({ status: 'requires_action', last_payment_error: error });
|
|
185
|
+
if (invoice) {
|
|
186
|
+
await invoice.update({
|
|
187
|
+
status: 'uncollectible',
|
|
188
|
+
attempt_count: invoice.attempt_count + 1,
|
|
189
|
+
attempted: true,
|
|
190
|
+
status_transitions: { ...invoice.status_transitions, marked_uncollectible_at: dayjs().unix() },
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
} else if (invoice) {
|
|
169
194
|
if (invoice.attempt_count > MAX_RETRY_COUNT) {
|
|
170
195
|
await paymentIntent.update({ status: 'requires_action', last_payment_error: error });
|
|
171
|
-
await invoice.update({
|
|
196
|
+
await invoice.update({
|
|
197
|
+
status: 'uncollectible',
|
|
198
|
+
attempt_count: invoice.attempt_count + 1,
|
|
199
|
+
attempted: true,
|
|
200
|
+
status_transitions: { ...invoice.status_transitions, marked_uncollectible_at: dayjs().unix() },
|
|
201
|
+
});
|
|
202
|
+
|
|
172
203
|
// FIXME: send email to customer, pause subscription
|
|
173
204
|
logger.error('PaymentIntent capture failed after max retry', { id: paymentIntent.id });
|
|
174
205
|
} else {
|
|
@@ -189,9 +220,6 @@ export const handlePayment = async (job: PaymentJob) => {
|
|
|
189
220
|
runAt: retryAt,
|
|
190
221
|
});
|
|
191
222
|
}
|
|
192
|
-
} else {
|
|
193
|
-
logger.error('PaymentIntent status reverted on capture error', { id: paymentIntent.id });
|
|
194
|
-
await paymentIntent.update({ status: 'requires_capture' });
|
|
195
223
|
}
|
|
196
224
|
}
|
|
197
225
|
};
|
|
@@ -2,10 +2,9 @@ import type { Transaction, TransferV3Tx } from '@ocap/client';
|
|
|
2
2
|
import { fromAddress } from '@ocap/wallet';
|
|
3
3
|
|
|
4
4
|
import { invoiceQueue } from '../../jobs/invoice';
|
|
5
|
-
import { paymentQueue } from '../../jobs/payment';
|
|
5
|
+
import { handlePaymentSucceed, paymentQueue } from '../../jobs/payment';
|
|
6
6
|
import type { CallbackArgs } from '../../libs/auth';
|
|
7
7
|
import { wallet } from '../../libs/auth';
|
|
8
|
-
import dayjs from '../../libs/dayjs';
|
|
9
8
|
import { getTxMetadata } from '../../libs/util';
|
|
10
9
|
import { ensureInvoiceForCollect, getAuthPrincipalClaim } from './shared';
|
|
11
10
|
|
|
@@ -83,16 +82,7 @@ export default {
|
|
|
83
82
|
},
|
|
84
83
|
});
|
|
85
84
|
|
|
86
|
-
await
|
|
87
|
-
paid: true,
|
|
88
|
-
status: 'paid',
|
|
89
|
-
amount_due: '0',
|
|
90
|
-
amount_paid: invoice.amount_due,
|
|
91
|
-
amount_remaining: '0',
|
|
92
|
-
attempt_count: invoice.attempt_count + 1,
|
|
93
|
-
status_transitions: { ...invoice.status_transitions, paid_at: dayjs().unix() },
|
|
94
|
-
collection_method: 'send_invoice',
|
|
95
|
-
});
|
|
85
|
+
await handlePaymentSucceed(paymentIntent);
|
|
96
86
|
|
|
97
87
|
// cleanup the queue
|
|
98
88
|
let exist = await paymentQueue.get(paymentIntent.id);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Transaction, TransferV3Tx } from '@ocap/client';
|
|
2
2
|
import { fromAddress } from '@ocap/wallet';
|
|
3
3
|
|
|
4
|
+
import { handlePaymentSucceed } from '../../jobs/payment';
|
|
4
5
|
import type { CallbackArgs } from '../../libs/auth';
|
|
5
6
|
import { wallet } from '../../libs/auth';
|
|
6
|
-
import dayjs from '../../libs/dayjs';
|
|
7
7
|
import { getTxMetadata } from '../../libs/util';
|
|
8
8
|
import { ensureInvoiceForCheckout, ensurePaymentIntent, getAuthPrincipalClaim } from './shared';
|
|
9
9
|
|
|
@@ -60,7 +60,7 @@ export default {
|
|
|
60
60
|
throw new Error('Payment intent not found');
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
await ensureInvoiceForCheckout({ checkoutSession, customer, paymentIntent });
|
|
64
64
|
|
|
65
65
|
if (paymentMethod.type === 'arcblock') {
|
|
66
66
|
await paymentIntent.update({ status: 'processing' });
|
|
@@ -79,16 +79,6 @@ export default {
|
|
|
79
79
|
{ headers: client.pickGasPayerHeaders(request) }
|
|
80
80
|
);
|
|
81
81
|
|
|
82
|
-
await checkoutSession.update({
|
|
83
|
-
status: 'complete',
|
|
84
|
-
payment_status: 'paid',
|
|
85
|
-
payment_details: {
|
|
86
|
-
arcblock: {
|
|
87
|
-
tx_hash: txHash,
|
|
88
|
-
payer: userDid,
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
82
|
await paymentIntent.update({
|
|
93
83
|
status: 'succeeded',
|
|
94
84
|
amount_received: paymentIntent.amount,
|
|
@@ -100,18 +90,7 @@ export default {
|
|
|
100
90
|
},
|
|
101
91
|
});
|
|
102
92
|
|
|
103
|
-
|
|
104
|
-
await invoice.update({
|
|
105
|
-
paid: true,
|
|
106
|
-
status: 'paid',
|
|
107
|
-
amount_due: '0',
|
|
108
|
-
amount_paid: paymentIntent.amount,
|
|
109
|
-
amount_remaining: '0',
|
|
110
|
-
attempt_count: invoice.attempt_count + 1,
|
|
111
|
-
attempted: true,
|
|
112
|
-
status_transitions: { ...invoice.status_transitions, paid_at: dayjs().unix() },
|
|
113
|
-
});
|
|
114
|
-
}
|
|
93
|
+
await handlePaymentSucceed(paymentIntent);
|
|
115
94
|
|
|
116
95
|
return { hash: txHash };
|
|
117
96
|
}
|
|
@@ -363,7 +363,10 @@ router.post('/:id/checkout/:priceId', async (req, res) => {
|
|
|
363
363
|
behavior: 'per_checkout_session',
|
|
364
364
|
factory: req.query.nft_mint_factory as string,
|
|
365
365
|
};
|
|
366
|
+
raw.nft_mint_status = 'pending';
|
|
366
367
|
logger.info('use nft_mint_settings from query when checkout from pricing table', { v: raw.nft_mint_settings });
|
|
368
|
+
} else {
|
|
369
|
+
raw.nft_mint_status = 'disabled';
|
|
367
370
|
}
|
|
368
371
|
|
|
369
372
|
const session = await CheckoutSession.create(raw as any);
|
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.45",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"@abtnode/types": "1.16.17",
|
|
104
104
|
"@arcblock/eslint-config": "^0.2.4",
|
|
105
105
|
"@arcblock/eslint-config-ts": "^0.2.4",
|
|
106
|
-
"@did-pay/types": "1.13.
|
|
106
|
+
"@did-pay/types": "1.13.45",
|
|
107
107
|
"@types/cookie-parser": "^1.4.5",
|
|
108
108
|
"@types/cors": "^2.8.15",
|
|
109
109
|
"@types/dotenv-flow": "^3.3.2",
|
|
@@ -140,5 +140,5 @@
|
|
|
140
140
|
"parser": "typescript"
|
|
141
141
|
}
|
|
142
142
|
},
|
|
143
|
-
"gitHead": "
|
|
143
|
+
"gitHead": "6dacad185a8f180bbb63ec062aa196dc07c56535"
|
|
144
144
|
}
|
|
@@ -28,6 +28,14 @@ const waitForCheckoutComplete = (sessionId: string) => {
|
|
|
28
28
|
return pWaitFor(
|
|
29
29
|
async () => {
|
|
30
30
|
const { data } = await api.get(`/api/checkout-sessions/retrieve/${sessionId}`);
|
|
31
|
+
if (
|
|
32
|
+
data.paymentIntent &&
|
|
33
|
+
data.paymentIntent.status === 'requires_action' &&
|
|
34
|
+
data.paymentIntent.last_payment_error
|
|
35
|
+
) {
|
|
36
|
+
throw new Error(data.paymentIntent.last_payment_error.message);
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
return (
|
|
32
40
|
data.checkoutSession?.status === 'complete' &&
|
|
33
41
|
['paid', 'no_payment_required'].includes(data.checkoutSession?.payment_status)
|