@sails-pay/bachs 0.0.1 → 0.0.2

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 CHANGED
@@ -4,6 +4,9 @@ module.exports = {
4
4
  identity: 'sails-bachs',
5
5
  config: {},
6
6
  checkout: methods.checkout,
7
+ customer: {
8
+ portal: methods.customer.portal
9
+ },
7
10
  verify: methods.verify,
8
11
  webhooks: {
9
12
  verify: methods.webhooks.verify
@@ -0,0 +1,57 @@
1
+ const fetch = require('../../helpers/fetch')
2
+ const parameters = require('../../helpers/parameters')
3
+
4
+ module.exports = require('machine').build({
5
+ friendlyName: 'Create customer portal session',
6
+ description:
7
+ 'Creates a fresh Bachs customer portal session and returns its hosted URL.',
8
+ moreInfoUrl:
9
+ 'https://docs.bachs.io/guides/customer-portal/create-portal-session',
10
+ inputs: {
11
+ apiKey: parameters.BACHS_API_KEY,
12
+ baseUrl: parameters.BACHS_BASE_URL,
13
+ customerId: {
14
+ type: 'string',
15
+ required: true,
16
+ description: 'The Bachs customer ID.'
17
+ }
18
+ },
19
+ exits: {
20
+ success: {
21
+ description: 'The fresh customer portal URL.',
22
+ outputVariableName: 'portalUrl',
23
+ outputType: 'string'
24
+ },
25
+ couldNotCreatePortalUrl: {
26
+ description: 'Customer portal URL could not be created.',
27
+ outputVariableName: 'errors',
28
+ outputType: 'ref'
29
+ }
30
+ },
31
+ fn: async function ({ apiKey, baseUrl, customerId }, exits) {
32
+ const adapterConfig = require('../../adapter').config
33
+
34
+ try {
35
+ const portalSession = await fetch(
36
+ `/customers/${encodeURIComponent(customerId)}/portal-sessions`,
37
+ {
38
+ method: 'POST',
39
+ apiKey: apiKey || adapterConfig.apiKey,
40
+ baseUrl: baseUrl || adapterConfig.baseUrl
41
+ }
42
+ )
43
+
44
+ if (
45
+ !portalSession ||
46
+ typeof portalSession.url !== 'string' ||
47
+ portalSession.url.length === 0
48
+ ) {
49
+ return exits.couldNotCreatePortalUrl(portalSession)
50
+ }
51
+
52
+ return exits.success(portalSession.url)
53
+ } catch (error) {
54
+ return exits.couldNotCreatePortalUrl(error.bachs || error)
55
+ }
56
+ }
57
+ })
package/machines/index.js CHANGED
@@ -4,6 +4,9 @@ checkout.get = require('./checkout/get')
4
4
 
5
5
  module.exports = {
6
6
  checkout,
7
+ customer: {
8
+ portal: require('./customer/portal')
9
+ },
7
10
  verify: require('./verify'),
8
11
  webhooks: {
9
12
  verify: require('./webhooks/verify')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sails-pay/bachs",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Bachs adapter for Sails Pay",
5
5
  "main": "adapter.js",
6
6
  "scripts": {
@@ -0,0 +1,183 @@
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
+ test('customer.portal creates a fresh sandbox portal session without a body', async () => {
12
+ const calls = []
13
+
14
+ fetch.setFetchImplementation(async (url, options) => {
15
+ calls.push({ url, options })
16
+
17
+ return {
18
+ ok: true,
19
+ status: 201,
20
+ statusText: 'Created',
21
+ text: async () =>
22
+ JSON.stringify({
23
+ id: `psn_${calls.length}`,
24
+ url: `https://portal.bachs.io/s/${calls.length}`
25
+ })
26
+ }
27
+ })
28
+
29
+ const firstPortalUrl = await adapter.customer.portal({
30
+ apiKey: 'sk_sandbox_123',
31
+ customerId: 'cust_123'
32
+ })
33
+ const secondPortalUrl = await adapter.customer.portal({
34
+ apiKey: 'sk_sandbox_123',
35
+ customerId: 'cust_123'
36
+ })
37
+
38
+ assert.equal(typeof adapter.customer.portal, 'function')
39
+ assert.equal(firstPortalUrl, 'https://portal.bachs.io/s/1')
40
+ assert.equal(secondPortalUrl, 'https://portal.bachs.io/s/2')
41
+ assert.equal(calls.length, 2)
42
+
43
+ for (const call of calls) {
44
+ assert.equal(
45
+ call.url,
46
+ 'https://sandbox-api.bachs.io/v1/customers/cust_123/portal-sessions'
47
+ )
48
+ assert.equal(call.options.method, 'POST')
49
+ assert.equal(call.options.headers.Authorization, 'Bearer sk_sandbox_123')
50
+ assert.equal(Object.hasOwn(call.options, 'body'), false)
51
+ }
52
+ })
53
+
54
+ test('customer.portal falls back to adapter configuration', async () => {
55
+ const calls = []
56
+ adapter.config = {
57
+ apiKey: 'sk_live_configured',
58
+ baseUrl: 'https://configured.example'
59
+ }
60
+
61
+ fetch.setFetchImplementation(async (url, options) => {
62
+ calls.push({ url, options })
63
+
64
+ return {
65
+ ok: true,
66
+ status: 201,
67
+ statusText: 'Created',
68
+ text: async () =>
69
+ JSON.stringify({
70
+ id: 'psn_configured',
71
+ url: 'https://portal.bachs.io/s/configured'
72
+ })
73
+ }
74
+ })
75
+
76
+ const portalUrl = await adapter.customer.portal({
77
+ customerId: 'cust_configured'
78
+ })
79
+
80
+ assert.equal(portalUrl, 'https://portal.bachs.io/s/configured')
81
+ assert.equal(
82
+ calls[0].url,
83
+ 'https://configured.example/v1/customers/cust_configured/portal-sessions'
84
+ )
85
+ assert.equal(
86
+ calls[0].options.headers.Authorization,
87
+ 'Bearer sk_live_configured'
88
+ )
89
+ })
90
+
91
+ test('customer.portal input credentials override adapter configuration', async () => {
92
+ const calls = []
93
+ adapter.config = {
94
+ apiKey: 'sk_live_configured',
95
+ baseUrl: 'https://configured.example'
96
+ }
97
+
98
+ fetch.setFetchImplementation(async (url, options) => {
99
+ calls.push({ url, options })
100
+
101
+ return {
102
+ ok: true,
103
+ status: 201,
104
+ statusText: 'Created',
105
+ text: async () =>
106
+ JSON.stringify({
107
+ id: 'psn_override',
108
+ url: 'https://portal.bachs.io/s/override'
109
+ })
110
+ }
111
+ })
112
+
113
+ await adapter.customer.portal({
114
+ apiKey: 'sk_sandbox_override',
115
+ baseUrl: 'https://override.example/api',
116
+ customerId: 'cust/override'
117
+ })
118
+
119
+ assert.equal(
120
+ calls[0].url,
121
+ 'https://override.example/v1/customers/cust%2Foverride/portal-sessions'
122
+ )
123
+ assert.equal(
124
+ calls[0].options.headers.Authorization,
125
+ 'Bearer sk_sandbox_override'
126
+ )
127
+ })
128
+
129
+ test('customer.portal rejects a malformed success response', async () => {
130
+ fetch.setFetchImplementation(async () => ({
131
+ ok: true,
132
+ status: 201,
133
+ statusText: 'Created',
134
+ text: async () => JSON.stringify({ id: 'psn_without_url' })
135
+ }))
136
+
137
+ await assert.rejects(
138
+ () =>
139
+ adapter.customer.portal({
140
+ apiKey: 'sk_sandbox_123',
141
+ customerId: 'cust_123'
142
+ }),
143
+ (error) => {
144
+ assert.equal(error.exit, 'couldNotCreatePortalUrl')
145
+ assert.deepEqual(error.raw, { id: 'psn_without_url' })
146
+ return true
147
+ }
148
+ )
149
+ })
150
+
151
+ for (const statusCode of [401, 403, 404, 429, 500, 503]) {
152
+ test(`customer.portal normalizes Bachs ${statusCode} responses`, async () => {
153
+ fetch.setFetchImplementation(async () => ({
154
+ ok: false,
155
+ status: statusCode,
156
+ statusText: 'Request failed',
157
+ text: async () =>
158
+ JSON.stringify({
159
+ detail: `Portal request failed with ${statusCode}`,
160
+ error_code: `STATUS_${statusCode}`
161
+ })
162
+ }))
163
+
164
+ await assert.rejects(
165
+ () =>
166
+ adapter.customer.portal({
167
+ apiKey: 'sk_sandbox_123',
168
+ customerId: 'cust_123'
169
+ }),
170
+ (error) => {
171
+ assert.equal(error.exit, 'couldNotCreatePortalUrl')
172
+ assert.equal(error.raw.statusCode, statusCode)
173
+ assert.equal(error.raw.statusText, 'Request failed')
174
+ assert.equal(error.raw.code, `STATUS_${statusCode}`)
175
+ assert.equal(
176
+ error.raw.message,
177
+ `Portal request failed with ${statusCode}`
178
+ )
179
+ return true
180
+ }
181
+ )
182
+ })
183
+ }