@birtalanrobert/commerce 1.0.0 → 2.0.0
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/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/migrations/1790000000000-AddSavedCardsAndKind.d.ts +30 -0
- package/dist/migrations/1790000000000-AddSavedCardsAndKind.d.ts.map +1 -0
- package/dist/migrations/1790000000000-AddSavedCardsAndKind.js +89 -0
- package/dist/migrations/1790000000000-AddSavedCardsAndKind.js.map +1 -0
- package/dist/nestjs/commerce.service.d.ts +65 -1
- package/dist/nestjs/commerce.service.d.ts.map +1 -1
- package/dist/nestjs/commerce.service.js +98 -2
- package/dist/nestjs/commerce.service.js.map +1 -1
- package/dist/nestjs/index.d.ts +6 -3
- package/dist/nestjs/index.d.ts.map +1 -1
- package/dist/nestjs/index.js +9 -3
- package/dist/nestjs/index.js.map +1 -1
- package/dist/nestjs/payment.entity.d.ts +11 -19
- package/dist/nestjs/payment.entity.d.ts.map +1 -1
- package/dist/nestjs/payment.entity.js +13 -0
- package/dist/nestjs/payment.entity.js.map +1 -1
- package/dist/nestjs/saved-card.entity.d.ts +42 -0
- package/dist/nestjs/saved-card.entity.d.ts.map +1 -0
- package/dist/nestjs/saved-card.entity.js +109 -0
- package/dist/nestjs/saved-card.entity.js.map +1 -0
- package/dist/payments.d.ts +54 -0
- package/dist/payments.d.ts.map +1 -0
- package/dist/payments.js +30 -0
- package/dist/payments.js.map +1 -0
- package/dist/providers/port.d.ts +68 -0
- package/dist/providers/port.d.ts.map +1 -1
- package/dist/providers/stripe.d.ts +12 -1
- package/dist/providers/stripe.d.ts.map +1 -1
- package/dist/providers/stripe.js +71 -1
- package/dist/providers/stripe.js.map +1 -1
- package/package.json +12 -4
- package/src/index.ts +11 -0
- package/src/migrations/1790000000000-AddSavedCardsAndKind.ts +92 -0
- package/src/nestjs/commerce.service.ts +170 -3
- package/src/nestjs/index.ts +15 -3
- package/src/nestjs/payment.entity.ts +17 -25
- package/src/nestjs/saved-card.entity.ts +69 -0
- package/src/payments.ts +67 -0
- package/src/providers/port.ts +74 -0
- package/src/providers/stripe.ts +90 -1
package/src/providers/stripe.ts
CHANGED
|
@@ -7,6 +7,9 @@ import type {
|
|
|
7
7
|
ProviderAccount,
|
|
8
8
|
ProviderEvent,
|
|
9
9
|
RefundRequest,
|
|
10
|
+
SaveCardRequest,
|
|
11
|
+
SaveCardResult,
|
|
12
|
+
StoredCard,
|
|
10
13
|
} from './port';
|
|
11
14
|
|
|
12
15
|
export interface StripeConnectOptions {
|
|
@@ -95,10 +98,31 @@ export class StripeConnect implements PaymentProvider {
|
|
|
95
98
|
...(request.applicationFee > 0 ? { application_fee_amount: request.applicationFee } : {}),
|
|
96
99
|
capture_method: request.capture ? 'automatic' : 'manual',
|
|
97
100
|
...(request.description ? { description: request.description } : {}),
|
|
101
|
+
...(request.customer ? { customer: request.customer } : {}),
|
|
102
|
+
/*
|
|
103
|
+
* A stored card is confirmed here and now, with nobody present.
|
|
104
|
+
*
|
|
105
|
+
* `off_session` is what tells the network the customer is not sitting
|
|
106
|
+
* there — it is also what makes the bank decline rather than ask for
|
|
107
|
+
* a code it has nobody to ask, which is the honest outcome for a fee
|
|
108
|
+
* charged three days after an appointment.
|
|
109
|
+
*/
|
|
110
|
+
...(request.paymentMethod
|
|
111
|
+
? {
|
|
112
|
+
payment_method: request.paymentMethod,
|
|
113
|
+
confirm: true,
|
|
114
|
+
off_session: true,
|
|
115
|
+
}
|
|
116
|
+
: {}),
|
|
98
117
|
// Carried through so a webhook can be matched back to what it paid
|
|
99
118
|
// for without a lookup table of our own.
|
|
100
119
|
metadata: { subject: request.subject },
|
|
101
|
-
|
|
120
|
+
/*
|
|
121
|
+
* Only where a card still has to be entered. Offering a redirect
|
|
122
|
+
* method to a charge that is already confirmed against a stored card
|
|
123
|
+
* is a contradiction the provider rejects.
|
|
124
|
+
*/
|
|
125
|
+
...(request.paymentMethod ? {} : { automatic_payment_methods: { enabled: true } }),
|
|
102
126
|
},
|
|
103
127
|
// The provider's own idempotency, so a retried request — a timeout, a
|
|
104
128
|
// double submit — does not charge somebody twice.
|
|
@@ -128,6 +152,70 @@ export class StripeConnect implements PaymentProvider {
|
|
|
128
152
|
await this.stripe.paymentIntents.cancel(externalId);
|
|
129
153
|
}
|
|
130
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Starts storing a card without charging it.
|
|
157
|
+
*
|
|
158
|
+
* The customer is created on **our** account rather than the business's. That
|
|
159
|
+
* is not a preference: under a destination charge the money lands on the
|
|
160
|
+
* business's account while the card is ours to charge, and a customer created
|
|
161
|
+
* on the business's account cannot be used from here at all.
|
|
162
|
+
*/
|
|
163
|
+
async saveCard(request: SaveCardRequest): Promise<SaveCardResult> {
|
|
164
|
+
const customer =
|
|
165
|
+
request.customer ??
|
|
166
|
+
(
|
|
167
|
+
await this.stripe.customers.create(
|
|
168
|
+
{ metadata: { subject: request.subject } },
|
|
169
|
+
{ idempotencyKey: `customer-${request.reference}` },
|
|
170
|
+
)
|
|
171
|
+
).id;
|
|
172
|
+
|
|
173
|
+
const intent = await this.stripe.setupIntents.create(
|
|
174
|
+
{
|
|
175
|
+
customer,
|
|
176
|
+
usage: 'off_session',
|
|
177
|
+
payment_method_types: ['card'],
|
|
178
|
+
metadata: { subject: request.subject },
|
|
179
|
+
},
|
|
180
|
+
{ idempotencyKey: request.reference },
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
customer,
|
|
185
|
+
externalId: intent.id,
|
|
186
|
+
// Non-null because a setup intent is created precisely to be confirmed.
|
|
187
|
+
clientSecret: intent.client_secret ?? '',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async storedCard(externalId: string): Promise<StoredCard | undefined> {
|
|
192
|
+
const intent = await this.stripe.setupIntents.retrieve(externalId, {
|
|
193
|
+
expand: ['payment_method'],
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const method = intent.payment_method;
|
|
197
|
+
if (!method || typeof method === 'string' || !intent.customer) return undefined;
|
|
198
|
+
|
|
199
|
+
const customer = typeof intent.customer === 'string' ? intent.customer : intent.customer.id;
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
customer,
|
|
203
|
+
paymentMethod: method.id,
|
|
204
|
+
...(method.card
|
|
205
|
+
? {
|
|
206
|
+
brand: method.card.brand,
|
|
207
|
+
last4: method.card.last4,
|
|
208
|
+
expiryMonth: method.card.exp_month,
|
|
209
|
+
expiryYear: method.card.exp_year,
|
|
210
|
+
}
|
|
211
|
+
: {}),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async forgetCard(paymentMethod: string): Promise<void> {
|
|
216
|
+
await this.stripe.paymentMethods.detach(paymentMethod);
|
|
217
|
+
}
|
|
218
|
+
|
|
131
219
|
async refund(request: RefundRequest): Promise<{ externalId: string }> {
|
|
132
220
|
const refund = await this.stripe.refunds.create(
|
|
133
221
|
{
|
|
@@ -205,6 +293,7 @@ function interpretIntent(intent: Stripe.PaymentIntent): ChargeResult {
|
|
|
205
293
|
return {
|
|
206
294
|
externalId: intent.id,
|
|
207
295
|
state,
|
|
296
|
+
...(intent.client_secret ? { clientSecret: intent.client_secret } : {}),
|
|
208
297
|
...(intent.next_action?.redirect_to_url?.url
|
|
209
298
|
? { redirectUrl: intent.next_action.redirect_to_url.url }
|
|
210
299
|
: {}),
|