Skip to content

Commit e8696dd

Browse files
authored
feat: sync payment intents (#70)
1 parent b48b71a commit e8696dd

File tree

5 files changed

+138
-7
lines changed

5 files changed

+138
-7
lines changed

README.md

+9-6
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,14 @@ This server synchronizes your Stripe account to a Postgres database. It can be a
6060
- [ ] `issuing_authorization.request`
6161
- [ ] `issuing_card.created`
6262
- [ ] `issuing_cardholder.created`
63-
- [ ] `payment_intent.amount_capturable_updated`
64-
- [ ] `payment_intent.canceled`
65-
- [ ] `payment_intent.created`
66-
- [ ] `payment_intent.payment_failed`
67-
- [ ] `payment_intent.succeeded`
63+
- [x] `payment_intent.amount_capturable_updated` 🟢
64+
- [x] `payment_intent.canceled` 🟢
65+
- [x] `payment_intent.created` 🟢
66+
- [x] `payment_intent.partially_refunded` 🟢
67+
- [x] `payment_intent.payment_failed` 🟢
68+
- [x] `payment_intent.processing` 🟢
69+
- [x] `payment_intent.requires_action` 🟢
70+
- [x] `payment_intent.succeeded` 🟢
6871
- [x] `payment_method.attached` 🟢
6972
- [x] `payment_method.automatically_updated` 🟢
7073
- [x] `payment_method.detached` 🟢
@@ -110,7 +113,7 @@ body: {
110113
}
111114
```
112115

113-
- `object` **all** | **customer** | **invoice** | **price** | **product** | **subscription** | **setup_intent** | **payment_method** | **dispute** | **charge**
116+
- `object` **all** | **customer** | **invoice** | **price** | **product** | **subscription** | **setup_intent** | **payment_method** | **dispute** | **charge** | **payment_intent**
114117
- `created` is Stripe.RangeQueryParam. It supports **gt**, **gte**, **lt**, **lte**
115118

116119
#### Alternative routes to sync `daily/weekly/monthly` data

db/migrations/0021_payment_intent.sql

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
create table if not exists "stripe"."payment_intents" (
2+
id text primary key,
3+
object text,
4+
amount integer,
5+
amount_capturable integer,
6+
amount_details jsonb,
7+
amount_received integer,
8+
application text,
9+
application_fee_amount integer,
10+
automatic_payment_methods text,
11+
canceled_at integer,
12+
cancellation_reason text,
13+
capture_method text,
14+
client_secret text,
15+
confirmation_method text,
16+
created integer,
17+
currency text,
18+
customer text,
19+
description text,
20+
invoice text,
21+
last_payment_error text,
22+
livemode boolean,
23+
metadata jsonb,
24+
next_action text,
25+
on_behalf_of text,
26+
payment_method text,
27+
payment_method_options jsonb,
28+
payment_method_types jsonb,
29+
processing text,
30+
receipt_email text,
31+
review text,
32+
setup_future_usage text,
33+
shipping jsonb,
34+
statement_descriptor text,
35+
statement_descriptor_suffix text,
36+
status text,
37+
transfer_data jsonb,
38+
transfer_group text
39+
);
40+
41+
CREATE INDEX stripe_payment_intents_customer_idx ON "stripe"."payment_intents" USING btree (customer);
42+
CREATE INDEX stripe_payment_intents_invoice_idx ON "stripe"."payment_intents" USING btree (invoice);

src/lib/payment_intents.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { getConfig } from '../utils/config'
2+
import Stripe from 'stripe'
3+
import { constructUpsertSql } from '../utils/helpers'
4+
import { backfillCustomers } from './customers'
5+
import { getUniqueIds, upsertMany } from './database_utils'
6+
import { backfillInvoices } from './invoices'
7+
import { paymentIntentSchema } from '../schemas/payment_intent'
8+
9+
const config = getConfig()
10+
11+
export const upsertPaymentIntents = async (
12+
paymentIntents: Stripe.PaymentIntent[]
13+
): Promise<Stripe.PaymentIntent[]> => {
14+
await Promise.all([
15+
backfillCustomers(getUniqueIds(paymentIntents, 'customer')),
16+
backfillInvoices(getUniqueIds(paymentIntents, 'invoice')),
17+
])
18+
19+
return upsertMany(paymentIntents, () =>
20+
constructUpsertSql(config.SCHEMA || 'stripe', 'payment_intents', paymentIntentSchema)
21+
)
22+
}

src/lib/sync.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { upsertCharges } from './charges'
1212
import { query } from '../utils/PostgresConnection'
1313
import { getConfig } from '../utils/config'
1414
import { pg as sql } from 'yesql'
15+
import { upsertPaymentIntents } from './payment_intents'
1516

1617
const config = getConfig()
1718

@@ -26,6 +27,7 @@ interface SyncBackfill {
2627
subscriptions?: Sync
2728
invoices?: Sync
2829
setupIntents?: Sync
30+
paymentIntents?: Sync
2931
paymentMethods?: Sync
3032
disputes?: Sync
3133
charges?: Sync
@@ -47,6 +49,7 @@ type SyncObject =
4749
| 'payment_method'
4850
| 'dispute'
4951
| 'charge'
52+
| 'payment_intent'
5053

5154
export async function syncBackfill(params?: SyncBackfillParams): Promise<SyncBackfill> {
5255
const { created, object } = params ?? {}
@@ -58,7 +61,8 @@ export async function syncBackfill(params?: SyncBackfillParams): Promise<SyncBac
5861
setupIntents,
5962
paymentMethods,
6063
disputes,
61-
charges
64+
charges,
65+
paymentIntents
6266

6367
switch (object) {
6468
case 'all':
@@ -70,6 +74,7 @@ export async function syncBackfill(params?: SyncBackfillParams): Promise<SyncBac
7074
charges = await syncCharges(created)
7175
setupIntents = await syncSetupIntents(created)
7276
paymentMethods = await syncPaymentMethods()
77+
paymentIntents = await syncPaymentIntents(created)
7378
break
7479
case 'customer':
7580
customers = await syncCustomers(created)
@@ -98,6 +103,9 @@ export async function syncBackfill(params?: SyncBackfillParams): Promise<SyncBac
98103
case 'charge':
99104
charges = await syncCharges(created)
100105
break
106+
case 'payment_intent':
107+
paymentIntents = await syncPaymentIntents(created)
108+
break
101109
default:
102110
break
103111
}
@@ -112,6 +120,7 @@ export async function syncBackfill(params?: SyncBackfillParams): Promise<SyncBac
112120
paymentMethods,
113121
disputes,
114122
charges,
123+
paymentIntents,
115124
}
116125
}
117126

@@ -178,6 +187,15 @@ export async function syncSetupIntents(created?: Stripe.RangeQueryParam): Promis
178187
return fetchAndUpsert(() => stripe.setupIntents.list(params), upsertSetupIntents)
179188
}
180189

190+
export async function syncPaymentIntents(created?: Stripe.RangeQueryParam): Promise<Sync> {
191+
console.log('Syncing payment_intents')
192+
193+
const params: Stripe.PaymentIntentListParams = { limit: 100 }
194+
if (created) params.created = created
195+
196+
return fetchAndUpsert(() => stripe.paymentIntents.list(params), upsertPaymentIntents)
197+
}
198+
181199
export async function syncPaymentMethods(): Promise<Sync> {
182200
// We can't filter by date here, it is also not possible to get payment methods without specifying a customer (you need Stripe Sigma for that -.-)
183201
// Thus, we need to loop through all customers

src/schemas/payment_intent.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { JsonSchema } from '../types/types'
2+
3+
export const paymentIntentSchema: JsonSchema = {
4+
$id: 'paymentIntentSchema',
5+
type: 'object',
6+
properties: {
7+
id: { type: 'text' },
8+
object: { type: 'text' },
9+
amount: { type: 'integer' },
10+
amount_capturable: { type: 'integer' },
11+
amount_details: { type: 'object' },
12+
amount_received: { type: 'integer' },
13+
application: { type: 'text' },
14+
application_fee_amount: { type: 'integer' },
15+
automatic_payment_methods: { type: 'text' },
16+
canceled_at: { type: 'integer' },
17+
cancellation_reason: { type: 'text' },
18+
capture_method: { type: 'text' },
19+
client_secret: { type: 'text' },
20+
confirmation_method: { type: 'text' },
21+
created: { type: 'integer' },
22+
currency: { type: 'text' },
23+
customer: { type: 'text' },
24+
description: { type: 'text' },
25+
invoice: { type: 'text' },
26+
last_payment_error: { type: 'text' },
27+
livemode: { type: 'boolean' },
28+
metadata: { type: 'object' },
29+
next_action: { type: 'text' },
30+
on_behalf_of: { type: 'text' },
31+
payment_method: { type: 'text' },
32+
payment_method_options: { type: 'object' },
33+
payment_method_types: { type: 'object' },
34+
processing: { type: 'text' },
35+
receipt_email: { type: 'text' },
36+
review: { type: 'text' },
37+
setup_future_usage: { type: 'text' },
38+
shipping: { type: 'object' },
39+
statement_descriptor: { type: 'text' },
40+
statement_descriptor_suffix: { type: 'text' },
41+
status: { type: 'text' },
42+
transfer_data: { type: 'object' },
43+
transfer_group: { type: 'text' },
44+
},
45+
required: ['id'],
46+
} as const

0 commit comments

Comments
 (0)