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.
@@ -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 { CheckoutSession, Invoice, PaymentIntent, PaymentMethod, TEventExpanded } from '../../../store/models';
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
- const checkoutSession = await CheckoutSession.findOne({ where: { payment_intent_id: paymentIntent.id } });
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
 
@@ -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
- if (invoice) {
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 (invoice && job.retryOnError) {
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({ status: 'uncollectible' });
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 invoice.update({
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
- const { invoice } = await ensureInvoiceForCheckout({ checkoutSession, customer, paymentIntent });
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
- if (invoice) {
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
  }
@@ -124,7 +124,6 @@ export default {
124
124
  },
125
125
  });
126
126
 
127
- // FIXME: handle error on the invoice payment job
128
127
  if (invoice) {
129
128
  invoiceQueue.push({ id: invoice.id, job: { invoiceId: invoice.id, retryOnError: false } });
130
129
  }
@@ -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
@@ -14,7 +14,7 @@ repository:
14
14
  type: git
15
15
  url: git+https://github.com/blocklet/payment-kit.git
16
16
  specVersion: 1.2.8
17
- version: 1.13.43
17
+ version: 1.13.45
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.13.43",
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.43",
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": "45e2930980a56ef53f1994ca52748972740bd505"
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)