@sails-pay/bachs 0.0.2 → 0.0.4
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/adapter.js +1 -0
- package/helpers/connect.js +86 -0
- package/helpers/payloads.js +48 -19
- package/helpers/validate-checkout-items.js +177 -0
- package/machines/checkout.js +15 -5
- package/machines/connect/account/create.js +77 -0
- package/machines/connect/account/get.js +49 -0
- package/machines/connect/account/link.js +71 -0
- package/machines/connect/balance/get.js +47 -0
- package/machines/connect/payout/create.js +114 -0
- package/machines/connect/transfer/create.js +71 -0
- package/machines/index.js +16 -0
- package/package.json +1 -1
- package/test/checkout.test.js +271 -6
- package/test/connect.test.js +390 -0
- package/test/payloads.test.js +171 -2
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
const { test, afterEach } = require('node:test')
|
|
2
|
+
const assert = require('node:assert/strict')
|
|
3
|
+
const adapter = require('../adapter')
|
|
4
|
+
const fetch = require('../helpers/fetch')
|
|
5
|
+
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
adapter.config = {}
|
|
8
|
+
fetch.resetFetchImplementation()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
function jsonResponse(body, status = 200) {
|
|
12
|
+
return {
|
|
13
|
+
ok: status >= 200 && status < 300,
|
|
14
|
+
status,
|
|
15
|
+
statusText: status >= 200 && status < 300 ? 'OK' : 'Request failed',
|
|
16
|
+
text: async () => JSON.stringify(body)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function recordCalls(responses) {
|
|
21
|
+
const calls = []
|
|
22
|
+
fetch.setFetchImplementation(async (url, options) => {
|
|
23
|
+
calls.push({ url, options })
|
|
24
|
+
const next = responses[calls.length - 1]
|
|
25
|
+
return jsonResponse(next.body, next.status)
|
|
26
|
+
})
|
|
27
|
+
return calls
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test('adapter exposes exactly the Connect surface', () => {
|
|
31
|
+
assert.deepEqual(Object.keys(adapter.connect).sort(), [
|
|
32
|
+
'account',
|
|
33
|
+
'balance',
|
|
34
|
+
'payout',
|
|
35
|
+
'transfer'
|
|
36
|
+
])
|
|
37
|
+
assert.deepEqual(Object.keys(adapter.connect.account).sort(), [
|
|
38
|
+
'create',
|
|
39
|
+
'get',
|
|
40
|
+
'link'
|
|
41
|
+
])
|
|
42
|
+
assert.deepEqual(Object.keys(adapter.connect.transfer), ['create'])
|
|
43
|
+
assert.deepEqual(Object.keys(adapter.connect.balance), ['get'])
|
|
44
|
+
assert.deepEqual(Object.keys(adapter.connect.payout), ['create'])
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('connect.account.create requests a recipient account and normalizes it', async () => {
|
|
48
|
+
const calls = recordCalls([
|
|
49
|
+
{
|
|
50
|
+
status: 201,
|
|
51
|
+
body: {
|
|
52
|
+
id: 'acct_123',
|
|
53
|
+
display_name: 'Ada Obi',
|
|
54
|
+
country: 'NG',
|
|
55
|
+
capabilities: {
|
|
56
|
+
transfers: { status: 'pending', requested: true },
|
|
57
|
+
payouts: { status: 'unrequested', requested: false }
|
|
58
|
+
},
|
|
59
|
+
requirements: { currently_due: ['payout_destination'] }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
const account = await adapter.connect.account.create({
|
|
65
|
+
apiKey: 'sk_sandbox_123',
|
|
66
|
+
email: 'ada@example.com',
|
|
67
|
+
name: 'Ada Obi',
|
|
68
|
+
idempotencyKey: 'maintainer-42'
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
assert.equal(calls[0].url, 'https://sandbox-api.bachs.io/v1/accounts')
|
|
72
|
+
assert.equal(calls[0].options.method, 'POST')
|
|
73
|
+
assert.equal(calls[0].options.headers['Idempotency-Key'], 'maintainer-42')
|
|
74
|
+
assert.equal(calls[0].options.headers['X-Account-Id'], undefined)
|
|
75
|
+
assert.deepEqual(JSON.parse(calls[0].options.body), {
|
|
76
|
+
contact_email: 'ada@example.com',
|
|
77
|
+
display_name: 'Ada Obi',
|
|
78
|
+
country: 'NG',
|
|
79
|
+
entity_type: 'individual',
|
|
80
|
+
configuration: {
|
|
81
|
+
recipient: {
|
|
82
|
+
capabilities: {
|
|
83
|
+
transfers: { requested: true },
|
|
84
|
+
payouts: { requested: true }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
assert.equal(account.id, 'acct_123')
|
|
91
|
+
assert.equal(account.email, 'ada@example.com')
|
|
92
|
+
assert.equal(account.name, 'Ada Obi')
|
|
93
|
+
assert.equal(account.country, 'NG')
|
|
94
|
+
assert.deepEqual(account.capabilities, {
|
|
95
|
+
transfers: 'pending',
|
|
96
|
+
payouts: 'inactive'
|
|
97
|
+
})
|
|
98
|
+
assert.deepEqual(account.requirements, ['payout_destination'])
|
|
99
|
+
assert.equal(account.raw.id, 'acct_123')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('connect.account.get reads the account by id', async () => {
|
|
103
|
+
const calls = recordCalls([
|
|
104
|
+
{
|
|
105
|
+
body: {
|
|
106
|
+
id: 'acct_1/2',
|
|
107
|
+
capabilities: { payouts: { status: 'active' } },
|
|
108
|
+
requirements: { currently_due: [] }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
])
|
|
112
|
+
|
|
113
|
+
const account = await adapter.connect.account.get({
|
|
114
|
+
apiKey: 'sk_sandbox_123',
|
|
115
|
+
account: 'acct_1/2'
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
assert.equal(
|
|
119
|
+
calls[0].url,
|
|
120
|
+
'https://sandbox-api.bachs.io/v1/accounts/acct_1%2F2'
|
|
121
|
+
)
|
|
122
|
+
assert.equal(calls[0].options.method, 'GET')
|
|
123
|
+
assert.deepEqual(account.capabilities, { payouts: 'active' })
|
|
124
|
+
assert.deepEqual(account.requirements, [])
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('connect.account.link creates a hosted onboarding link', async () => {
|
|
128
|
+
const calls = recordCalls([
|
|
129
|
+
{
|
|
130
|
+
body: {
|
|
131
|
+
url: 'https://connect.bachs.io/l/abc',
|
|
132
|
+
expires_at: '2026-09-14T16:00:00Z'
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
])
|
|
136
|
+
|
|
137
|
+
const link = await adapter.connect.account.link({
|
|
138
|
+
apiKey: 'sk_sandbox_123',
|
|
139
|
+
account: 'acct_123',
|
|
140
|
+
returnUrl: 'https://flossafrica.com/payouts/return',
|
|
141
|
+
refreshUrl: 'https://flossafrica.com/payouts/refresh'
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
assert.equal(
|
|
145
|
+
calls[0].url,
|
|
146
|
+
'https://sandbox-api.bachs.io/v1/accounts/acct_123/account-links'
|
|
147
|
+
)
|
|
148
|
+
assert.deepEqual(JSON.parse(calls[0].options.body), {
|
|
149
|
+
type: 'onboarding',
|
|
150
|
+
refresh_url: 'https://flossafrica.com/payouts/refresh',
|
|
151
|
+
return_url: 'https://flossafrica.com/payouts/return'
|
|
152
|
+
})
|
|
153
|
+
assert.equal(link.url, 'https://connect.bachs.io/l/abc')
|
|
154
|
+
assert.equal(link.expiresAt, '2026-09-14T16:00:00Z')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test('connect.transfer.create moves platform funds to the account', async () => {
|
|
158
|
+
const calls = recordCalls([
|
|
159
|
+
{
|
|
160
|
+
status: 201,
|
|
161
|
+
body: {
|
|
162
|
+
id: 'tr_1',
|
|
163
|
+
destination: 'acct_123',
|
|
164
|
+
amount: '10000.00',
|
|
165
|
+
currency: 'NGN',
|
|
166
|
+
transfer_group: 'pool-2026-09',
|
|
167
|
+
status: 'paid'
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
])
|
|
171
|
+
|
|
172
|
+
const transfer = await adapter.connect.transfer.create({
|
|
173
|
+
apiKey: 'sk_sandbox_123',
|
|
174
|
+
account: 'acct_123',
|
|
175
|
+
amount: '10000.00',
|
|
176
|
+
currency: 'NGN',
|
|
177
|
+
group: 'pool-2026-09',
|
|
178
|
+
idempotencyKey: 'pool-2026-09-acct_123'
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
assert.equal(calls[0].url, 'https://sandbox-api.bachs.io/v1/transfers')
|
|
182
|
+
assert.equal(calls[0].options.headers['X-Account-Id'], undefined)
|
|
183
|
+
assert.equal(
|
|
184
|
+
calls[0].options.headers['Idempotency-Key'],
|
|
185
|
+
'pool-2026-09-acct_123'
|
|
186
|
+
)
|
|
187
|
+
assert.deepEqual(JSON.parse(calls[0].options.body), {
|
|
188
|
+
destination: 'acct_123',
|
|
189
|
+
amount: '10000.00',
|
|
190
|
+
currency: 'NGN',
|
|
191
|
+
transfer_group: 'pool-2026-09'
|
|
192
|
+
})
|
|
193
|
+
assert.deepEqual(
|
|
194
|
+
{ ...transfer, raw: undefined },
|
|
195
|
+
{
|
|
196
|
+
id: 'tr_1',
|
|
197
|
+
account: 'acct_123',
|
|
198
|
+
amount: '10000.00',
|
|
199
|
+
currency: 'NGN',
|
|
200
|
+
group: 'pool-2026-09',
|
|
201
|
+
status: 'paid',
|
|
202
|
+
raw: undefined
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('connect.balance.get acts as the account and returns one entry per currency', async () => {
|
|
208
|
+
const calls = recordCalls([
|
|
209
|
+
{
|
|
210
|
+
body: {
|
|
211
|
+
account_id: 'acct_123',
|
|
212
|
+
balances: [
|
|
213
|
+
{
|
|
214
|
+
currency: 'NGN',
|
|
215
|
+
available_balance: '12000.00',
|
|
216
|
+
pending_balance: '0.00'
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
currency: 'USD',
|
|
220
|
+
available_balance: '5.00',
|
|
221
|
+
pending_balance: '1.00'
|
|
222
|
+
}
|
|
223
|
+
],
|
|
224
|
+
total_balance_usd: '13.00'
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
])
|
|
228
|
+
|
|
229
|
+
const balances = await adapter.connect.balance.get({
|
|
230
|
+
apiKey: 'sk_sandbox_123',
|
|
231
|
+
account: 'acct_123'
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
assert.equal(calls[0].url, 'https://sandbox-api.bachs.io/v1/balances')
|
|
235
|
+
assert.equal(calls[0].options.headers['X-Account-Id'], 'acct_123')
|
|
236
|
+
assert.deepEqual(balances, [
|
|
237
|
+
{ currency: 'NGN', available: '12000.00', pending: '0.00' },
|
|
238
|
+
{ currency: 'USD', available: '5.00', pending: '1.00' }
|
|
239
|
+
])
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test('connect.payout.create resolves the default destination for the currency', async () => {
|
|
243
|
+
const calls = recordCalls([
|
|
244
|
+
{
|
|
245
|
+
body: {
|
|
246
|
+
destinations: [
|
|
247
|
+
{ id: 'pd_old', is_usable: true, is_default: false },
|
|
248
|
+
{ id: 'pd_broken', is_usable: false, is_default: false },
|
|
249
|
+
{ id: 'pd_default', is_usable: true, is_default: true }
|
|
250
|
+
],
|
|
251
|
+
total: 3,
|
|
252
|
+
limit: 20,
|
|
253
|
+
offset: 0
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
body: {
|
|
258
|
+
id: 'pay_1',
|
|
259
|
+
status: 'processing',
|
|
260
|
+
amount: '9000.00',
|
|
261
|
+
currency: 'NGN',
|
|
262
|
+
fee: '50.00',
|
|
263
|
+
total_debited: '9050.00',
|
|
264
|
+
destination: 'pd_default'
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
])
|
|
268
|
+
|
|
269
|
+
const payout = await adapter.connect.payout.create({
|
|
270
|
+
apiKey: 'sk_sandbox_123',
|
|
271
|
+
account: 'acct_123',
|
|
272
|
+
amount: '9000.00',
|
|
273
|
+
currency: 'NGN',
|
|
274
|
+
reference: 'withdrawal-1',
|
|
275
|
+
idempotencyKey: 'withdrawal-1-attempt-1'
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
assert.equal(
|
|
279
|
+
calls[0].url,
|
|
280
|
+
'https://sandbox-api.bachs.io/v1/payouts/destinations?currency=NGN'
|
|
281
|
+
)
|
|
282
|
+
assert.equal(calls[0].options.headers['X-Account-Id'], 'acct_123')
|
|
283
|
+
assert.equal(calls[1].url, 'https://sandbox-api.bachs.io/v1/payouts')
|
|
284
|
+
assert.equal(calls[1].options.headers['X-Account-Id'], 'acct_123')
|
|
285
|
+
assert.equal(
|
|
286
|
+
calls[1].options.headers['Idempotency-Key'],
|
|
287
|
+
'withdrawal-1-attempt-1'
|
|
288
|
+
)
|
|
289
|
+
assert.deepEqual(JSON.parse(calls[1].options.body), {
|
|
290
|
+
destination: 'pd_default',
|
|
291
|
+
amount: '9000.00',
|
|
292
|
+
reference: 'withdrawal-1'
|
|
293
|
+
})
|
|
294
|
+
assert.deepEqual(
|
|
295
|
+
{ ...payout, raw: undefined },
|
|
296
|
+
{
|
|
297
|
+
id: 'pay_1',
|
|
298
|
+
account: 'acct_123',
|
|
299
|
+
amount: '9000.00',
|
|
300
|
+
currency: 'NGN',
|
|
301
|
+
fee: '50.00',
|
|
302
|
+
destination: 'pd_default',
|
|
303
|
+
status: 'pending',
|
|
304
|
+
raw: undefined
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
test('connect.payout.create uses an explicit destination without a lookup', async () => {
|
|
310
|
+
const calls = recordCalls([
|
|
311
|
+
{
|
|
312
|
+
body: {
|
|
313
|
+
id: 'pay_2',
|
|
314
|
+
status: 'completed',
|
|
315
|
+
amount: '100.00',
|
|
316
|
+
currency: 'NGN',
|
|
317
|
+
destination: 'pd_1'
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
])
|
|
321
|
+
|
|
322
|
+
const payout = await adapter.connect.payout.create({
|
|
323
|
+
apiKey: 'sk_sandbox_123',
|
|
324
|
+
account: 'acct_123',
|
|
325
|
+
amount: '100.00',
|
|
326
|
+
destination: 'pd_1',
|
|
327
|
+
idempotencyKey: 'withdrawal-2'
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
assert.equal(calls.length, 1)
|
|
331
|
+
assert.equal(payout.status, 'paid')
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
test('connect.payout.create exits noDestination when nothing is usable', async () => {
|
|
335
|
+
recordCalls([
|
|
336
|
+
{
|
|
337
|
+
body: {
|
|
338
|
+
destinations: [{ id: 'pd_review', is_usable: false }],
|
|
339
|
+
total: 1,
|
|
340
|
+
limit: 20,
|
|
341
|
+
offset: 0
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
])
|
|
345
|
+
|
|
346
|
+
await assert.rejects(
|
|
347
|
+
adapter.connect.payout.create({
|
|
348
|
+
apiKey: 'sk_sandbox_123',
|
|
349
|
+
account: 'acct_123',
|
|
350
|
+
amount: '100.00',
|
|
351
|
+
currency: 'NGN'
|
|
352
|
+
}),
|
|
353
|
+
(error) => error.exit === 'noDestination'
|
|
354
|
+
)
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
test('connect.payout.create exits noDestination without a destination or currency', async () => {
|
|
358
|
+
const calls = recordCalls([])
|
|
359
|
+
|
|
360
|
+
await assert.rejects(
|
|
361
|
+
adapter.connect.payout.create({
|
|
362
|
+
apiKey: 'sk_sandbox_123',
|
|
363
|
+
account: 'acct_123',
|
|
364
|
+
amount: '100.00'
|
|
365
|
+
}),
|
|
366
|
+
(error) => error.exit === 'noDestination'
|
|
367
|
+
)
|
|
368
|
+
assert.equal(calls.length, 0)
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
test('Connect errors surface the normalized Bachs error', async () => {
|
|
372
|
+
recordCalls([
|
|
373
|
+
{
|
|
374
|
+
status: 400,
|
|
375
|
+
body: { error_code: 'INSUFFICIENT_BALANCE', detail: 'Not enough funds' }
|
|
376
|
+
}
|
|
377
|
+
])
|
|
378
|
+
|
|
379
|
+
await assert.rejects(
|
|
380
|
+
adapter.connect.transfer.create({
|
|
381
|
+
apiKey: 'sk_sandbox_123',
|
|
382
|
+
account: 'acct_123',
|
|
383
|
+
amount: '1.00',
|
|
384
|
+
currency: 'NGN'
|
|
385
|
+
}),
|
|
386
|
+
(error) =>
|
|
387
|
+
error.exit === 'couldNotCreateTransfer' &&
|
|
388
|
+
error.raw.code === 'INSUFFICIENT_BALANCE'
|
|
389
|
+
)
|
|
390
|
+
})
|
package/test/payloads.test.js
CHANGED
|
@@ -3,9 +3,147 @@ const assert = require('node:assert/strict')
|
|
|
3
3
|
const {
|
|
4
4
|
buildCheckoutSessionPayload,
|
|
5
5
|
buildPureCheckoutPayload,
|
|
6
|
-
buildRefundPayload
|
|
6
|
+
buildRefundPayload,
|
|
7
|
+
buildProductCart,
|
|
8
|
+
normalizePricing
|
|
7
9
|
} = require('../helpers/payloads')
|
|
8
10
|
|
|
11
|
+
test('buildProductCart preserves catalog pricing and optional quantity', () => {
|
|
12
|
+
assert.deepEqual(buildProductCart([{ product: 'prod_catalog' }]), [
|
|
13
|
+
{
|
|
14
|
+
product_id: 'prod_catalog'
|
|
15
|
+
}
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
assert.deepEqual(
|
|
19
|
+
buildProductCart([{ product: 'prod_quantity', quantity: 2 }]),
|
|
20
|
+
[
|
|
21
|
+
{
|
|
22
|
+
product_id: 'prod_quantity',
|
|
23
|
+
quantity: 2
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('buildProductCart normalizes fixed pricing shorthand and explicit pricing', () => {
|
|
30
|
+
const fixedPricing = {
|
|
31
|
+
product_id: 'prod_fixed',
|
|
32
|
+
pricing: {
|
|
33
|
+
price_type: 'fixed',
|
|
34
|
+
amount: '19.00'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
assert.deepEqual(
|
|
39
|
+
buildProductCart([{ product: 'prod_fixed', amount: '19.00' }]),
|
|
40
|
+
[fixedPricing]
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
assert.deepEqual(
|
|
44
|
+
buildProductCart([
|
|
45
|
+
{
|
|
46
|
+
product: 'prod_fixed',
|
|
47
|
+
pricing: {
|
|
48
|
+
type: 'fixed',
|
|
49
|
+
amount: '19.00'
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
]),
|
|
53
|
+
[fixedPricing]
|
|
54
|
+
)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('buildProductCart normalizes custom pricing and chosenAmount', () => {
|
|
58
|
+
assert.deepEqual(
|
|
59
|
+
buildProductCart([
|
|
60
|
+
{
|
|
61
|
+
product: 'prod_custom',
|
|
62
|
+
pricing: {
|
|
63
|
+
type: 'custom',
|
|
64
|
+
presetAmount: '10.00',
|
|
65
|
+
minimumAmount: '5.00',
|
|
66
|
+
maximumAmount: '100.00'
|
|
67
|
+
},
|
|
68
|
+
chosenAmount: '12.00'
|
|
69
|
+
}
|
|
70
|
+
]),
|
|
71
|
+
[
|
|
72
|
+
{
|
|
73
|
+
product_id: 'prod_custom',
|
|
74
|
+
pricing: {
|
|
75
|
+
price_type: 'custom',
|
|
76
|
+
preset_amount: '10.00',
|
|
77
|
+
minimum_amount: '5.00',
|
|
78
|
+
maximum_amount: '100.00'
|
|
79
|
+
},
|
|
80
|
+
amount: '12.00'
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('buildProductCart normalizes free pricing', () => {
|
|
87
|
+
assert.deepEqual(
|
|
88
|
+
buildProductCart([
|
|
89
|
+
{
|
|
90
|
+
product: 'prod_free',
|
|
91
|
+
pricing: {
|
|
92
|
+
type: 'free'
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
]),
|
|
96
|
+
[
|
|
97
|
+
{
|
|
98
|
+
product_id: 'prod_free',
|
|
99
|
+
pricing: {
|
|
100
|
+
price_type: 'free'
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('buildProductCart maps catalog chosenAmount to the flat Bachs amount', () => {
|
|
108
|
+
assert.deepEqual(
|
|
109
|
+
buildProductCart([
|
|
110
|
+
{
|
|
111
|
+
product: 'prod_catalog_custom',
|
|
112
|
+
chosenAmount: '12.00'
|
|
113
|
+
}
|
|
114
|
+
]),
|
|
115
|
+
[
|
|
116
|
+
{
|
|
117
|
+
product_id: 'prod_catalog_custom',
|
|
118
|
+
amount: '12.00'
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('normalizePricing maps reusable pricing fields to Bachs snake case', () => {
|
|
125
|
+
assert.deepEqual(
|
|
126
|
+
normalizePricing({
|
|
127
|
+
type: 'custom',
|
|
128
|
+
presetAmount: '10.00',
|
|
129
|
+
minimumAmount: '5.00',
|
|
130
|
+
maximumAmount: '100.00',
|
|
131
|
+
currencyOptions: {
|
|
132
|
+
NGN: '15000.00'
|
|
133
|
+
}
|
|
134
|
+
}),
|
|
135
|
+
{
|
|
136
|
+
price_type: 'custom',
|
|
137
|
+
preset_amount: '10.00',
|
|
138
|
+
minimum_amount: '5.00',
|
|
139
|
+
maximum_amount: '100.00',
|
|
140
|
+
currency_options: {
|
|
141
|
+
NGN: '15000.00'
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
})
|
|
146
|
+
|
|
9
147
|
test('buildCheckoutSessionPayload maps product checkout inputs to Bachs snake case', () => {
|
|
10
148
|
const payload = buildCheckoutSessionPayload(
|
|
11
149
|
{
|
|
@@ -37,7 +175,10 @@ test('buildCheckoutSessionPayload maps product checkout inputs to Bachs snake ca
|
|
|
37
175
|
{
|
|
38
176
|
product_id: 'prod_abc123',
|
|
39
177
|
quantity: 2,
|
|
40
|
-
|
|
178
|
+
pricing: {
|
|
179
|
+
price_type: 'fixed',
|
|
180
|
+
amount: '50.00'
|
|
181
|
+
}
|
|
41
182
|
}
|
|
42
183
|
],
|
|
43
184
|
billing_currency: 'NGN',
|
|
@@ -118,6 +259,34 @@ test('buildPureCheckoutPayload maps amount checkout inputs to Bachs snake case',
|
|
|
118
259
|
})
|
|
119
260
|
})
|
|
120
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
|
+
|
|
121
290
|
test('buildRefundPayload maps refund inputs to Bachs snake case', () => {
|
|
122
291
|
const payload = buildRefundPayload({
|
|
123
292
|
chargeId: 'chr_123',
|