@sails-pay/bachs 0.0.5 → 0.0.7
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/helpers/parameters.js +2 -1
- package/helpers/payloads.js +8 -28
- package/helpers/validate-checkout-connect.js +1 -1
- package/machines/checkout.js +29 -20
- package/package.json +1 -1
- package/test/checkout.test.js +73 -12
- package/test/payloads.test.js +0 -72
package/helpers/parameters.js
CHANGED
|
@@ -37,7 +37,8 @@ module.exports = {
|
|
|
37
37
|
BACHS_SUCCESS_URL: {
|
|
38
38
|
type: 'string',
|
|
39
39
|
friendlyName: 'Success URL',
|
|
40
|
-
description:
|
|
40
|
+
description:
|
|
41
|
+
'Default URL Bachs redirects to after payment. Alias for returnUrl.'
|
|
41
42
|
},
|
|
42
43
|
BACHS_CANCEL_URL: {
|
|
43
44
|
type: 'string',
|
package/helpers/payloads.js
CHANGED
|
@@ -65,6 +65,7 @@ function buildProductCart(items) {
|
|
|
65
65
|
function buildCheckoutSessionPayload(inputs, adapterConfig = {}) {
|
|
66
66
|
const productCollectionId =
|
|
67
67
|
inputs.productCollectionId || inputs.productCollection
|
|
68
|
+
const productCart = buildProductCart(inputs.items)
|
|
68
69
|
const checkoutData = inputs.checkoutData || {}
|
|
69
70
|
const returnUrl =
|
|
70
71
|
inputs.returnUrl ||
|
|
@@ -74,14 +75,20 @@ function buildCheckoutSessionPayload(inputs, adapterConfig = {}) {
|
|
|
74
75
|
|
|
75
76
|
return withoutUndefined({
|
|
76
77
|
customer: buildCustomerPayload(inputs),
|
|
77
|
-
product_cart:
|
|
78
|
+
product_cart: productCart,
|
|
78
79
|
product_collection_id: productCollectionId,
|
|
80
|
+
// A session prices itself with products or with a raw amount, never both.
|
|
81
|
+
pricing:
|
|
82
|
+
productCart || productCollectionId
|
|
83
|
+
? undefined
|
|
84
|
+
: buildPricingPayload(inputs),
|
|
79
85
|
billing_currency: inputs.billingCurrency,
|
|
80
86
|
allowed_payment_method_types: inputs.allowedPaymentMethodTypes,
|
|
81
87
|
return_url: returnUrl,
|
|
82
88
|
cancel_url: inputs.cancelUrl || adapterConfig.cancelUrl,
|
|
83
89
|
reference: inputs.reference,
|
|
84
90
|
metadata: inputs.metadata || checkoutData.custom,
|
|
91
|
+
expires_in_minutes: inputs.expiresInMinutes,
|
|
85
92
|
transfer_data: inputs.connect && {
|
|
86
93
|
destination: inputs.connect.destination
|
|
87
94
|
},
|
|
@@ -129,32 +136,6 @@ function buildPricingPayload(inputs) {
|
|
|
129
136
|
})
|
|
130
137
|
}
|
|
131
138
|
|
|
132
|
-
function buildPureCheckoutPayload(inputs, adapterConfig = {}) {
|
|
133
|
-
const checkoutData = inputs.checkoutData || {}
|
|
134
|
-
const customer = inputs.customer || {}
|
|
135
|
-
|
|
136
|
-
return withoutUndefined({
|
|
137
|
-
pricing: buildPricingPayload(inputs),
|
|
138
|
-
customer_email:
|
|
139
|
-
inputs.customerEmail ||
|
|
140
|
-
inputs.email ||
|
|
141
|
-
customer.email ||
|
|
142
|
-
checkoutData.email,
|
|
143
|
-
customer_name:
|
|
144
|
-
inputs.customerName || inputs.name || customer.name || checkoutData.name,
|
|
145
|
-
success_url:
|
|
146
|
-
inputs.successUrl ||
|
|
147
|
-
inputs.returnUrl ||
|
|
148
|
-
adapterConfig.successUrl ||
|
|
149
|
-
adapterConfig.returnUrl,
|
|
150
|
-
cancel_url: inputs.cancelUrl || adapterConfig.cancelUrl,
|
|
151
|
-
reference: inputs.reference,
|
|
152
|
-
metadata: inputs.metadata || checkoutData.custom,
|
|
153
|
-
expires_in_minutes: inputs.expiresInMinutes,
|
|
154
|
-
simulated_outcome: inputs.simulatedOutcome
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
|
|
158
139
|
function buildRefundPayload(inputs) {
|
|
159
140
|
return withoutUndefined({
|
|
160
141
|
charge_id: inputs.chargeId,
|
|
@@ -170,7 +151,6 @@ function buildRefundPayload(inputs) {
|
|
|
170
151
|
|
|
171
152
|
module.exports = {
|
|
172
153
|
buildCheckoutSessionPayload,
|
|
173
|
-
buildPureCheckoutPayload,
|
|
174
154
|
buildRefundPayload,
|
|
175
155
|
buildProductCart,
|
|
176
156
|
buildCustomerPayload,
|
|
@@ -20,7 +20,7 @@ function validateCheckoutConnect(connect, { isCheckoutSession }) {
|
|
|
20
20
|
if (!isCheckoutSession) {
|
|
21
21
|
return invalid(
|
|
22
22
|
'connect',
|
|
23
|
-
'is only supported on
|
|
23
|
+
'is only supported on checkout sessions (items, productCollectionId, or pricing).'
|
|
24
24
|
)
|
|
25
25
|
}
|
|
26
26
|
|
package/machines/checkout.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
const fetch = require('../helpers/fetch')
|
|
2
|
-
const {
|
|
3
|
-
buildCheckoutSessionPayload,
|
|
4
|
-
buildPureCheckoutPayload
|
|
5
|
-
} = require('../helpers/payloads')
|
|
2
|
+
const { buildCheckoutSessionPayload } = require('../helpers/payloads')
|
|
6
3
|
const validateCheckoutItems = require('../helpers/validate-checkout-items')
|
|
7
4
|
const validateCheckoutConnect = require('../helpers/validate-checkout-connect')
|
|
8
5
|
const parameters = require('../helpers/parameters')
|
|
@@ -46,8 +43,7 @@ module.exports = require('machine').build({
|
|
|
46
43
|
},
|
|
47
44
|
successUrl: {
|
|
48
45
|
type: 'string',
|
|
49
|
-
description:
|
|
50
|
-
'URL Bachs redirects to after a pure checkout payment. Maps to success_url.'
|
|
46
|
+
description: 'Alias for returnUrl.'
|
|
51
47
|
},
|
|
52
48
|
cancelUrl: parameters.BACHS_CANCEL_URL,
|
|
53
49
|
customer: {
|
|
@@ -96,23 +92,27 @@ module.exports = require('machine').build({
|
|
|
96
92
|
},
|
|
97
93
|
pricing: {
|
|
98
94
|
type: 'ref',
|
|
99
|
-
description:
|
|
95
|
+
description:
|
|
96
|
+
'Pricing for a checkout that sets its own price, instead of products.'
|
|
100
97
|
},
|
|
101
98
|
amount: {
|
|
102
99
|
type: 'string',
|
|
103
|
-
description:
|
|
100
|
+
description:
|
|
101
|
+
'Amount for a checkout that sets its own price, e.g. "50.00".'
|
|
104
102
|
},
|
|
105
103
|
currency: {
|
|
106
104
|
type: 'string',
|
|
107
|
-
description:
|
|
105
|
+
description:
|
|
106
|
+
'Currency for a checkout that sets its own price, e.g. "USD" or "NGN".'
|
|
108
107
|
},
|
|
109
108
|
currencyOptions: {
|
|
110
109
|
type: 'ref',
|
|
111
|
-
description:
|
|
110
|
+
description:
|
|
111
|
+
'Per-currency amount overrides for a checkout that sets its own price.'
|
|
112
112
|
},
|
|
113
113
|
expiresInMinutes: {
|
|
114
114
|
type: 'number',
|
|
115
|
-
description: '
|
|
115
|
+
description: 'Checkout expiry in minutes.'
|
|
116
116
|
},
|
|
117
117
|
simulatedOutcome: {
|
|
118
118
|
type: 'string',
|
|
@@ -147,9 +147,17 @@ module.exports = require('machine').build({
|
|
|
147
147
|
const hasProductCollection = Boolean(
|
|
148
148
|
inputs.productCollectionId || inputs.productCollection
|
|
149
149
|
)
|
|
150
|
-
const
|
|
150
|
+
const hasPricing = Boolean(
|
|
151
|
+
inputs.pricing || inputs.amount !== undefined || inputs.currency
|
|
152
|
+
)
|
|
153
|
+
// Bachs checks out through sessions. A session takes products or a raw
|
|
154
|
+
// price, and it permits a split rather than requiring one, so a checkout
|
|
155
|
+
// that states its own amount and currency belongs here whether or not it
|
|
156
|
+
// pays a connected account.
|
|
157
|
+
const shouldUseCheckoutSession =
|
|
158
|
+
hasItems || hasProductCollection || hasPricing
|
|
151
159
|
|
|
152
|
-
if (
|
|
160
|
+
if (hasItems && hasProductCollection) {
|
|
153
161
|
return exits.invalidRequest({
|
|
154
162
|
message:
|
|
155
163
|
'Provide exactly one of items or productCollectionId for a Bachs checkout session.'
|
|
@@ -174,20 +182,21 @@ module.exports = require('machine').build({
|
|
|
174
182
|
}
|
|
175
183
|
}
|
|
176
184
|
|
|
177
|
-
const
|
|
178
|
-
const payload = shouldUseCheckoutSession
|
|
179
|
-
? buildCheckoutSessionPayload(inputs, adapterConfig)
|
|
180
|
-
: buildPureCheckoutPayload(inputs, adapterConfig)
|
|
185
|
+
const payload = buildCheckoutSessionPayload(inputs, adapterConfig)
|
|
181
186
|
|
|
182
|
-
if (
|
|
187
|
+
if (
|
|
188
|
+
!payload.product_cart &&
|
|
189
|
+
!payload.product_collection_id &&
|
|
190
|
+
!payload.pricing
|
|
191
|
+
) {
|
|
183
192
|
return exits.invalidRequest({
|
|
184
193
|
message:
|
|
185
|
-
'Provide
|
|
194
|
+
'Provide items, productCollectionId, or pricing for a Bachs checkout.'
|
|
186
195
|
})
|
|
187
196
|
}
|
|
188
197
|
|
|
189
198
|
try {
|
|
190
|
-
const checkout = await fetch(
|
|
199
|
+
const checkout = await fetch('/checkout-sessions', {
|
|
191
200
|
method: 'POST',
|
|
192
201
|
apiKey: inputs.apiKey || adapterConfig.apiKey,
|
|
193
202
|
baseUrl: inputs.baseUrl || adapterConfig.baseUrl,
|
package/package.json
CHANGED
package/test/checkout.test.js
CHANGED
|
@@ -112,7 +112,7 @@ test('checkout sends custom pricing and the buyer-selected amount', async () =>
|
|
|
112
112
|
})
|
|
113
113
|
})
|
|
114
114
|
|
|
115
|
-
test('checkout
|
|
115
|
+
test('checkout prices itself with amount and currency', async () => {
|
|
116
116
|
const calls = []
|
|
117
117
|
|
|
118
118
|
fetch.setFetchImplementation(async (url, options) => {
|
|
@@ -137,9 +137,13 @@ test('checkout preserves pure checkout behavior', async () => {
|
|
|
137
137
|
})
|
|
138
138
|
|
|
139
139
|
assert.equal(checkoutUrl, 'https://pay.bachs.io/c/pure')
|
|
140
|
-
assert.equal(
|
|
140
|
+
assert.equal(
|
|
141
|
+
calls[0].url,
|
|
142
|
+
'https://sandbox-api.bachs.io/v1/checkout-sessions'
|
|
143
|
+
)
|
|
141
144
|
assert.equal(calls[0].options.headers['Idempotency-Key'], 'pure_123')
|
|
142
145
|
assert.deepEqual(JSON.parse(calls[0].options.body), {
|
|
146
|
+
customer: {},
|
|
143
147
|
pricing: {
|
|
144
148
|
currency: 'USD',
|
|
145
149
|
amount: '42.00'
|
|
@@ -405,6 +409,73 @@ test('checkout splits a checkout session with a connected account', async () =>
|
|
|
405
409
|
assert.equal(body.connect, undefined)
|
|
406
410
|
})
|
|
407
411
|
|
|
412
|
+
test('checkout splits a product-less checkout session', async () => {
|
|
413
|
+
const calls = []
|
|
414
|
+
|
|
415
|
+
fetch.setFetchImplementation(async (url, options) => {
|
|
416
|
+
calls.push({ url, options })
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
ok: true,
|
|
420
|
+
status: 201,
|
|
421
|
+
statusText: 'Created',
|
|
422
|
+
text: async () =>
|
|
423
|
+
JSON.stringify({ checkout_url: 'https://pay.bachs.io/c/raw-split' })
|
|
424
|
+
}
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
const checkoutUrl = await checkout({
|
|
428
|
+
apiKey: 'sk_sandbox_123',
|
|
429
|
+
amount: '10500.00',
|
|
430
|
+
currency: 'NGN',
|
|
431
|
+
billingCurrency: 'NGN',
|
|
432
|
+
customer: { email: 'sponsor@example.com' },
|
|
433
|
+
reference: 'sponsorship_2',
|
|
434
|
+
connect: { destination: 'acct_123', platformFee: '500.00' }
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
assert.equal(checkoutUrl, 'https://pay.bachs.io/c/raw-split')
|
|
438
|
+
assert.equal(
|
|
439
|
+
calls[0].url,
|
|
440
|
+
'https://sandbox-api.bachs.io/v1/checkout-sessions'
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
const body = JSON.parse(calls[0].options.body)
|
|
444
|
+
assert.deepEqual(body.pricing, { currency: 'NGN', amount: '10500.00' })
|
|
445
|
+
assert.deepEqual(body.transfer_data, { destination: 'acct_123' })
|
|
446
|
+
assert.equal(body.platform_fee, '500.00')
|
|
447
|
+
assert.equal(body.billing_currency, 'NGN')
|
|
448
|
+
assert.equal('product_cart' in body, false)
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
test('a self-priced checkout without connect still uses a checkout session', async () => {
|
|
452
|
+
const calls = []
|
|
453
|
+
|
|
454
|
+
fetch.setFetchImplementation(async (url, options) => {
|
|
455
|
+
calls.push({ url, options })
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
ok: true,
|
|
459
|
+
status: 201,
|
|
460
|
+
statusText: 'Created',
|
|
461
|
+
text: async () =>
|
|
462
|
+
JSON.stringify({ checkout_url: 'https://pay.bachs.io/c/raw' })
|
|
463
|
+
}
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
await checkout({
|
|
467
|
+
apiKey: 'sk_sandbox_123',
|
|
468
|
+
amount: '10500.00',
|
|
469
|
+
currency: 'NGN',
|
|
470
|
+
customer: { email: 'sponsor@example.com' }
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
assert.equal(
|
|
474
|
+
calls[0].url,
|
|
475
|
+
'https://sandbox-api.bachs.io/v1/checkout-sessions'
|
|
476
|
+
)
|
|
477
|
+
})
|
|
478
|
+
|
|
408
479
|
test('checkout without connect sends no split fields', async () => {
|
|
409
480
|
const calls = []
|
|
410
481
|
|
|
@@ -437,16 +508,6 @@ const invalidConnectCases = [
|
|
|
437
508
|
field: 'connect',
|
|
438
509
|
message: /must be an object/
|
|
439
510
|
},
|
|
440
|
-
{
|
|
441
|
-
name: 'connect on a pure checkout',
|
|
442
|
-
inputs: {
|
|
443
|
-
amount: '10500.00',
|
|
444
|
-
currency: 'NGN',
|
|
445
|
-
connect: { destination: 'acct_123', platformFee: '500.00' }
|
|
446
|
-
},
|
|
447
|
-
field: 'connect',
|
|
448
|
-
message: /only supported on product checkout sessions/
|
|
449
|
-
},
|
|
450
511
|
{
|
|
451
512
|
name: 'connect without a destination',
|
|
452
513
|
inputs: {
|
package/test/payloads.test.js
CHANGED
|
@@ -2,7 +2,6 @@ const test = require('node:test')
|
|
|
2
2
|
const assert = require('node:assert/strict')
|
|
3
3
|
const {
|
|
4
4
|
buildCheckoutSessionPayload,
|
|
5
|
-
buildPureCheckoutPayload,
|
|
6
5
|
buildRefundPayload,
|
|
7
6
|
buildProductCart,
|
|
8
7
|
normalizePricing
|
|
@@ -216,77 +215,6 @@ test('buildCheckoutSessionPayload maps productCollectionId and configured return
|
|
|
216
215
|
})
|
|
217
216
|
})
|
|
218
217
|
|
|
219
|
-
test('buildPureCheckoutPayload maps amount checkout inputs to Bachs snake case', () => {
|
|
220
|
-
const payload = buildPureCheckoutPayload(
|
|
221
|
-
{
|
|
222
|
-
amount: '50.00',
|
|
223
|
-
currency: 'USD',
|
|
224
|
-
currencyOptions: {
|
|
225
|
-
NGN: '75000.00'
|
|
226
|
-
},
|
|
227
|
-
email: 'customer@example.com',
|
|
228
|
-
name: 'Jane Doe',
|
|
229
|
-
successUrl: 'https://example.com/success',
|
|
230
|
-
cancelUrl: 'https://example.com/cancel',
|
|
231
|
-
reference: 'order_9876',
|
|
232
|
-
metadata: {
|
|
233
|
-
orderId: '9876'
|
|
234
|
-
},
|
|
235
|
-
expiresInMinutes: 30,
|
|
236
|
-
simulatedOutcome: 'success'
|
|
237
|
-
},
|
|
238
|
-
{}
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
assert.deepEqual(payload, {
|
|
242
|
-
pricing: {
|
|
243
|
-
currency: 'USD',
|
|
244
|
-
amount: '50.00',
|
|
245
|
-
currency_options: {
|
|
246
|
-
NGN: '75000.00'
|
|
247
|
-
}
|
|
248
|
-
},
|
|
249
|
-
customer_email: 'customer@example.com',
|
|
250
|
-
customer_name: 'Jane Doe',
|
|
251
|
-
success_url: 'https://example.com/success',
|
|
252
|
-
cancel_url: 'https://example.com/cancel',
|
|
253
|
-
reference: 'order_9876',
|
|
254
|
-
metadata: {
|
|
255
|
-
orderId: '9876'
|
|
256
|
-
},
|
|
257
|
-
expires_in_minutes: 30,
|
|
258
|
-
simulated_outcome: 'success'
|
|
259
|
-
})
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
test('buildPureCheckoutPayload reuses advanced pricing normalization', () => {
|
|
263
|
-
const payload = buildPureCheckoutPayload({
|
|
264
|
-
pricing: {
|
|
265
|
-
type: 'custom',
|
|
266
|
-
currency: 'USD',
|
|
267
|
-
presetAmount: '10.00',
|
|
268
|
-
minimumAmount: '5.00',
|
|
269
|
-
maximumAmount: '100.00'
|
|
270
|
-
},
|
|
271
|
-
currencyOptions: {
|
|
272
|
-
NGN: '15000.00'
|
|
273
|
-
}
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
assert.deepEqual(payload, {
|
|
277
|
-
pricing: {
|
|
278
|
-
currency: 'USD',
|
|
279
|
-
price_type: 'custom',
|
|
280
|
-
preset_amount: '10.00',
|
|
281
|
-
minimum_amount: '5.00',
|
|
282
|
-
maximum_amount: '100.00',
|
|
283
|
-
currency_options: {
|
|
284
|
-
NGN: '15000.00'
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
})
|
|
288
|
-
})
|
|
289
|
-
|
|
290
218
|
test('buildRefundPayload maps refund inputs to Bachs snake case', () => {
|
|
291
219
|
const payload = buildRefundPayload({
|
|
292
220
|
chargeId: 'chr_123',
|