@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
package/adapter.js
CHANGED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translates Bachs Connect responses into the provider-agnostic shapes
|
|
3
|
+
* returned by `sails.pay.connect`. Every adapter that implements Connect
|
|
4
|
+
* returns these same shapes, so application code never reads provider fields.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function capabilityStatus(capability) {
|
|
8
|
+
const status = capability && capability.status
|
|
9
|
+
if (status === 'active' || status === 'pending') return status
|
|
10
|
+
return 'inactive'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function toAccount(account, { email } = {}) {
|
|
14
|
+
const capabilities = {}
|
|
15
|
+
for (const [name, capability] of Object.entries(
|
|
16
|
+
(account && account.capabilities) || {}
|
|
17
|
+
)) {
|
|
18
|
+
capabilities[name] = capabilityStatus(capability)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
id: account.id,
|
|
23
|
+
email: account.contact_email || email || null,
|
|
24
|
+
name: account.display_name || account.name || null,
|
|
25
|
+
country: account.country || null,
|
|
26
|
+
capabilities,
|
|
27
|
+
requirements:
|
|
28
|
+
(account.requirements && account.requirements.currently_due) || [],
|
|
29
|
+
raw: account
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function toLink(link) {
|
|
34
|
+
return {
|
|
35
|
+
url: link.url,
|
|
36
|
+
expiresAt: link.expires_at || null,
|
|
37
|
+
raw: link
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function toTransfer(transfer) {
|
|
42
|
+
return {
|
|
43
|
+
id: transfer.id,
|
|
44
|
+
account: transfer.destination,
|
|
45
|
+
amount: transfer.amount,
|
|
46
|
+
currency: transfer.currency,
|
|
47
|
+
group: transfer.transfer_group || null,
|
|
48
|
+
status: transfer.status === 'paid' ? 'paid' : 'pending',
|
|
49
|
+
raw: transfer
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function toBalances(response) {
|
|
54
|
+
return ((response && response.balances) || []).map((balance) => ({
|
|
55
|
+
currency: balance.currency,
|
|
56
|
+
available: balance.available_balance,
|
|
57
|
+
pending: balance.pending_balance
|
|
58
|
+
}))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function payoutStatus(status) {
|
|
62
|
+
if (status === 'completed') return 'paid'
|
|
63
|
+
if (status === 'failed') return 'failed'
|
|
64
|
+
return 'pending'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function toPayout(payout, { account }) {
|
|
68
|
+
return {
|
|
69
|
+
id: payout.id,
|
|
70
|
+
account,
|
|
71
|
+
amount: payout.amount,
|
|
72
|
+
currency: payout.currency,
|
|
73
|
+
fee: payout.fee || null,
|
|
74
|
+
destination: payout.destination || null,
|
|
75
|
+
status: payoutStatus(payout.status),
|
|
76
|
+
raw: payout
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
toAccount,
|
|
82
|
+
toLink,
|
|
83
|
+
toTransfer,
|
|
84
|
+
toBalances,
|
|
85
|
+
toPayout
|
|
86
|
+
}
|
package/helpers/payloads.js
CHANGED
|
@@ -44,13 +44,22 @@ function buildProductCart(items) {
|
|
|
44
44
|
return undefined
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
return items.map((item) =>
|
|
48
|
-
|
|
47
|
+
return items.map((item) => {
|
|
48
|
+
const pricing =
|
|
49
|
+
item.amount !== undefined
|
|
50
|
+
? normalizePricing({
|
|
51
|
+
type: 'fixed',
|
|
52
|
+
amount: item.amount
|
|
53
|
+
})
|
|
54
|
+
: normalizePricing(item.pricing)
|
|
55
|
+
|
|
56
|
+
return withoutUndefined({
|
|
49
57
|
product_id: item.product || item.productId,
|
|
50
58
|
quantity: item.quantity,
|
|
51
|
-
|
|
59
|
+
pricing,
|
|
60
|
+
amount: item.chosenAmount
|
|
52
61
|
})
|
|
53
|
-
)
|
|
62
|
+
})
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
function buildCheckoutSessionPayload(inputs, adapterConfig = {}) {
|
|
@@ -76,25 +85,44 @@ function buildCheckoutSessionPayload(inputs, adapterConfig = {}) {
|
|
|
76
85
|
})
|
|
77
86
|
}
|
|
78
87
|
|
|
79
|
-
function
|
|
80
|
-
|
|
88
|
+
function normalizePricing(pricing, fallbackCurrencyOptions) {
|
|
89
|
+
if (!pricing || typeof pricing !== 'object' || Array.isArray(pricing)) {
|
|
90
|
+
return undefined
|
|
91
|
+
}
|
|
81
92
|
|
|
82
|
-
|
|
83
|
-
|
|
93
|
+
const {
|
|
94
|
+
type,
|
|
95
|
+
presetAmount,
|
|
96
|
+
minimumAmount,
|
|
97
|
+
maximumAmount,
|
|
98
|
+
currencyOptions,
|
|
99
|
+
...pricingInput
|
|
100
|
+
} = pricing
|
|
101
|
+
const normalizedPricing = withoutUndefined({
|
|
102
|
+
...pricingInput,
|
|
103
|
+
price_type: type,
|
|
104
|
+
preset_amount: presetAmount,
|
|
105
|
+
minimum_amount: minimumAmount,
|
|
106
|
+
maximum_amount: maximumAmount,
|
|
107
|
+
currency_options:
|
|
108
|
+
currencyOptions === undefined ? fallbackCurrencyOptions : currencyOptions
|
|
109
|
+
})
|
|
84
110
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
currency_options: inputs.currencyOptions
|
|
94
|
-
})
|
|
111
|
+
return Object.keys(normalizedPricing).length > 0
|
|
112
|
+
? normalizedPricing
|
|
113
|
+
: undefined
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function buildPricingPayload(inputs) {
|
|
117
|
+
if (inputs.pricing) {
|
|
118
|
+
return normalizePricing(inputs.pricing, inputs.currencyOptions)
|
|
95
119
|
}
|
|
96
120
|
|
|
97
|
-
return
|
|
121
|
+
return normalizePricing({
|
|
122
|
+
currency: inputs.currency,
|
|
123
|
+
amount: inputs.amount,
|
|
124
|
+
currencyOptions: inputs.currencyOptions
|
|
125
|
+
})
|
|
98
126
|
}
|
|
99
127
|
|
|
100
128
|
function buildPureCheckoutPayload(inputs, adapterConfig = {}) {
|
|
@@ -142,5 +170,6 @@ module.exports = {
|
|
|
142
170
|
buildRefundPayload,
|
|
143
171
|
buildProductCart,
|
|
144
172
|
buildCustomerPayload,
|
|
173
|
+
normalizePricing,
|
|
145
174
|
withoutUndefined
|
|
146
175
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
const pricingTypes = new Set(['fixed', 'custom', 'free'])
|
|
2
|
+
const pricingMoneyFields = [
|
|
3
|
+
'amount',
|
|
4
|
+
'presetAmount',
|
|
5
|
+
'minimumAmount',
|
|
6
|
+
'maximumAmount'
|
|
7
|
+
]
|
|
8
|
+
const customPricingFields = ['presetAmount', 'minimumAmount', 'maximumAmount']
|
|
9
|
+
const decimalStringPattern = /^\d+(?:\.\d+)?$/
|
|
10
|
+
|
|
11
|
+
function fieldWasProvided(object, field) {
|
|
12
|
+
return object[field] !== undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function invalid(field, message) {
|
|
16
|
+
return {
|
|
17
|
+
field,
|
|
18
|
+
message: `${field} ${message}`
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function validateMoney(value, field) {
|
|
23
|
+
if (typeof value !== 'string' || !decimalStringPattern.test(value)) {
|
|
24
|
+
return invalid(field, 'must be a non-negative decimal string.')
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validatePricingMoney(pricing, itemPath) {
|
|
29
|
+
for (const field of pricingMoneyFields) {
|
|
30
|
+
if (fieldWasProvided(pricing, field)) {
|
|
31
|
+
const error = validateMoney(
|
|
32
|
+
pricing[field],
|
|
33
|
+
`${itemPath}.pricing.${field}`
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if (error) {
|
|
37
|
+
return error
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function validateItem(item, index) {
|
|
44
|
+
const itemPath = `items[${index}]`
|
|
45
|
+
|
|
46
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
47
|
+
return invalid(itemPath, 'must be an object.')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (
|
|
51
|
+
fieldWasProvided(item, 'quantity') &&
|
|
52
|
+
(!Number.isInteger(item.quantity) || item.quantity < 1)
|
|
53
|
+
) {
|
|
54
|
+
return invalid(`${itemPath}.quantity`, 'must be an integer of at least 1.')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const hasAmount = fieldWasProvided(item, 'amount')
|
|
58
|
+
const hasPricing = fieldWasProvided(item, 'pricing')
|
|
59
|
+
const hasChosenAmount = fieldWasProvided(item, 'chosenAmount')
|
|
60
|
+
|
|
61
|
+
if (hasAmount && hasPricing) {
|
|
62
|
+
return invalid(
|
|
63
|
+
itemPath,
|
|
64
|
+
'must not provide both amount and pricing; amount is the fixed-price shorthand.'
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (hasAmount) {
|
|
69
|
+
const error = validateMoney(item.amount, `${itemPath}.amount`)
|
|
70
|
+
|
|
71
|
+
if (error) {
|
|
72
|
+
return error
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (hasChosenAmount) {
|
|
76
|
+
return invalid(
|
|
77
|
+
`${itemPath}.chosenAmount`,
|
|
78
|
+
'can only be used with catalog custom pricing or ad-hoc custom pricing.'
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (hasChosenAmount) {
|
|
84
|
+
const error = validateMoney(item.chosenAmount, `${itemPath}.chosenAmount`)
|
|
85
|
+
|
|
86
|
+
if (error) {
|
|
87
|
+
return error
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!hasPricing) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
!item.pricing ||
|
|
97
|
+
typeof item.pricing !== 'object' ||
|
|
98
|
+
Array.isArray(item.pricing)
|
|
99
|
+
) {
|
|
100
|
+
return invalid(`${itemPath}.pricing`, 'must be an object.')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const pricing = item.pricing
|
|
104
|
+
|
|
105
|
+
if (!pricingTypes.has(pricing.type)) {
|
|
106
|
+
return invalid(
|
|
107
|
+
`${itemPath}.pricing.type`,
|
|
108
|
+
'must be fixed, custom, or free.'
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (pricing.type === 'fixed') {
|
|
113
|
+
if (!fieldWasProvided(pricing, 'amount')) {
|
|
114
|
+
return invalid(
|
|
115
|
+
`${itemPath}.pricing.amount`,
|
|
116
|
+
'is required for fixed pricing.'
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const customField = customPricingFields.find((field) =>
|
|
121
|
+
fieldWasProvided(pricing, field)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if (customField) {
|
|
125
|
+
return invalid(
|
|
126
|
+
`${itemPath}.pricing.${customField}`,
|
|
127
|
+
'is not allowed with fixed pricing.'
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (hasChosenAmount) {
|
|
132
|
+
return invalid(
|
|
133
|
+
`${itemPath}.chosenAmount`,
|
|
134
|
+
'can only be used with catalog custom pricing or ad-hoc custom pricing.'
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (pricing.type === 'custom' && fieldWasProvided(pricing, 'amount')) {
|
|
140
|
+
return invalid(
|
|
141
|
+
`${itemPath}.pricing.amount`,
|
|
142
|
+
'is not allowed with custom pricing.'
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (pricing.type === 'free') {
|
|
147
|
+
const monetaryField = pricingMoneyFields.find((field) =>
|
|
148
|
+
fieldWasProvided(pricing, field)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if (monetaryField) {
|
|
152
|
+
return invalid(
|
|
153
|
+
`${itemPath}.pricing.${monetaryField}`,
|
|
154
|
+
'is not allowed with free pricing.'
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (hasChosenAmount) {
|
|
159
|
+
return invalid(
|
|
160
|
+
`${itemPath}.chosenAmount`,
|
|
161
|
+
'is not allowed with free pricing.'
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return validatePricingMoney(pricing, itemPath)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = function validateCheckoutItems(items) {
|
|
170
|
+
for (let index = 0; index < items.length; index++) {
|
|
171
|
+
const error = validateItem(items[index], index)
|
|
172
|
+
|
|
173
|
+
if (error) {
|
|
174
|
+
return error
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
package/machines/checkout.js
CHANGED
|
@@ -3,6 +3,7 @@ const {
|
|
|
3
3
|
buildCheckoutSessionPayload,
|
|
4
4
|
buildPureCheckoutPayload
|
|
5
5
|
} = require('../helpers/payloads')
|
|
6
|
+
const validateCheckoutItems = require('../helpers/validate-checkout-items')
|
|
6
7
|
const parameters = require('../helpers/parameters')
|
|
7
8
|
|
|
8
9
|
module.exports = require('machine').build({
|
|
@@ -16,7 +17,7 @@ module.exports = require('machine').build({
|
|
|
16
17
|
items: {
|
|
17
18
|
type: 'ref',
|
|
18
19
|
description:
|
|
19
|
-
'Product items for Bachs Checkout Sessions.
|
|
20
|
+
'Product items for Bachs Checkout Sessions. Supports catalog pricing, fixed amount shorthand, advanced pricing, and chosenAmount.'
|
|
20
21
|
},
|
|
21
22
|
productCollectionId: {
|
|
22
23
|
type: 'string',
|
|
@@ -141,10 +142,6 @@ module.exports = require('machine').build({
|
|
|
141
142
|
inputs.productCollectionId || inputs.productCollection
|
|
142
143
|
)
|
|
143
144
|
const shouldUseCheckoutSession = hasItems || hasProductCollection
|
|
144
|
-
const path = shouldUseCheckoutSession ? '/checkout-sessions' : '/checkouts'
|
|
145
|
-
const payload = shouldUseCheckoutSession
|
|
146
|
-
? buildCheckoutSessionPayload(inputs, adapterConfig)
|
|
147
|
-
: buildPureCheckoutPayload(inputs, adapterConfig)
|
|
148
145
|
|
|
149
146
|
if (shouldUseCheckoutSession && hasItems === hasProductCollection) {
|
|
150
147
|
return exits.invalidRequest({
|
|
@@ -153,6 +150,19 @@ module.exports = require('machine').build({
|
|
|
153
150
|
})
|
|
154
151
|
}
|
|
155
152
|
|
|
153
|
+
if (hasItems) {
|
|
154
|
+
const validationError = validateCheckoutItems(inputs.items)
|
|
155
|
+
|
|
156
|
+
if (validationError) {
|
|
157
|
+
return exits.invalidRequest(validationError)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const path = shouldUseCheckoutSession ? '/checkout-sessions' : '/checkouts'
|
|
162
|
+
const payload = shouldUseCheckoutSession
|
|
163
|
+
? buildCheckoutSessionPayload(inputs, adapterConfig)
|
|
164
|
+
: buildPureCheckoutPayload(inputs, adapterConfig)
|
|
165
|
+
|
|
156
166
|
if (!shouldUseCheckoutSession && !payload.pricing) {
|
|
157
167
|
return exits.invalidRequest({
|
|
158
168
|
message:
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const fetch = require('../../../helpers/fetch')
|
|
2
|
+
const parameters = require('../../../helpers/parameters')
|
|
3
|
+
const { toAccount } = require('../../../helpers/connect')
|
|
4
|
+
|
|
5
|
+
module.exports = require('machine').build({
|
|
6
|
+
friendlyName: 'Create connected account',
|
|
7
|
+
description:
|
|
8
|
+
'Creates a recipient account with its own balance that the platform can transfer to and that can withdraw.',
|
|
9
|
+
moreInfoUrl: 'https://docs.bachs.io/connect/guides/create-an-account',
|
|
10
|
+
inputs: {
|
|
11
|
+
apiKey: parameters.BACHS_API_KEY,
|
|
12
|
+
baseUrl: parameters.BACHS_BASE_URL,
|
|
13
|
+
email: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
required: true,
|
|
16
|
+
isEmail: true,
|
|
17
|
+
description: 'Contact email of the person or business being onboarded.'
|
|
18
|
+
},
|
|
19
|
+
name: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Display name for the account.'
|
|
22
|
+
},
|
|
23
|
+
country: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
defaultsTo: 'NG',
|
|
26
|
+
description: 'ISO 3166-1 alpha-2 country of the account.'
|
|
27
|
+
},
|
|
28
|
+
capabilities: {
|
|
29
|
+
type: ['string'],
|
|
30
|
+
defaultsTo: ['transfers', 'payouts'],
|
|
31
|
+
description: 'Capabilities to request, e.g. ["transfers", "payouts"].'
|
|
32
|
+
},
|
|
33
|
+
idempotencyKey: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Prevents a retry from creating a duplicate account.'
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
exits: {
|
|
39
|
+
success: {
|
|
40
|
+
description: 'The created account.',
|
|
41
|
+
outputVariableName: 'account',
|
|
42
|
+
outputType: 'ref'
|
|
43
|
+
},
|
|
44
|
+
couldNotCreateAccount: {
|
|
45
|
+
description: 'The account could not be created.',
|
|
46
|
+
outputVariableName: 'error',
|
|
47
|
+
outputType: 'ref'
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
fn: async function (inputs, exits) {
|
|
51
|
+
const adapterConfig = require('../../../adapter').config
|
|
52
|
+
const capabilities = {}
|
|
53
|
+
for (const capability of inputs.capabilities) {
|
|
54
|
+
capabilities[capability] = { requested: true }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const account = await fetch('/accounts', {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
apiKey: inputs.apiKey || adapterConfig.apiKey,
|
|
61
|
+
baseUrl: inputs.baseUrl || adapterConfig.baseUrl,
|
|
62
|
+
idempotencyKey: inputs.idempotencyKey,
|
|
63
|
+
body: {
|
|
64
|
+
contact_email: inputs.email,
|
|
65
|
+
...(inputs.name && { display_name: inputs.name }),
|
|
66
|
+
country: inputs.country,
|
|
67
|
+
entity_type: 'individual',
|
|
68
|
+
configuration: { recipient: { capabilities } }
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
return exits.success(toAccount(account, { email: inputs.email }))
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return exits.couldNotCreateAccount(error.bachs || error)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const fetch = require('../../../helpers/fetch')
|
|
2
|
+
const parameters = require('../../../helpers/parameters')
|
|
3
|
+
const { toAccount } = require('../../../helpers/connect')
|
|
4
|
+
|
|
5
|
+
module.exports = require('machine').build({
|
|
6
|
+
friendlyName: 'Get connected account',
|
|
7
|
+
description:
|
|
8
|
+
'Retrieves a connected account with its capability statuses and currently due requirements.',
|
|
9
|
+
moreInfoUrl: 'https://docs.bachs.io/connect/accounts',
|
|
10
|
+
inputs: {
|
|
11
|
+
apiKey: parameters.BACHS_API_KEY,
|
|
12
|
+
baseUrl: parameters.BACHS_BASE_URL,
|
|
13
|
+
account: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
required: true,
|
|
16
|
+
description: 'The connected account ID.'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
exits: {
|
|
20
|
+
success: {
|
|
21
|
+
description: 'The connected account.',
|
|
22
|
+
outputVariableName: 'account',
|
|
23
|
+
outputType: 'ref'
|
|
24
|
+
},
|
|
25
|
+
couldNotGetAccount: {
|
|
26
|
+
description: 'The account could not be retrieved.',
|
|
27
|
+
outputVariableName: 'error',
|
|
28
|
+
outputType: 'ref'
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
fn: async function (inputs, exits) {
|
|
32
|
+
const adapterConfig = require('../../../adapter').config
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const account = await fetch(
|
|
36
|
+
`/accounts/${encodeURIComponent(inputs.account)}`,
|
|
37
|
+
{
|
|
38
|
+
method: 'GET',
|
|
39
|
+
apiKey: inputs.apiKey || adapterConfig.apiKey,
|
|
40
|
+
baseUrl: inputs.baseUrl || adapterConfig.baseUrl
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return exits.success(toAccount(account))
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return exits.couldNotGetAccount(error.bachs || error)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const fetch = require('../../../helpers/fetch')
|
|
2
|
+
const parameters = require('../../../helpers/parameters')
|
|
3
|
+
const { toLink } = require('../../../helpers/connect')
|
|
4
|
+
|
|
5
|
+
module.exports = require('machine').build({
|
|
6
|
+
friendlyName: 'Create connected account link',
|
|
7
|
+
description:
|
|
8
|
+
'Creates a short-lived hosted link that walks a connected account through its outstanding requirements.',
|
|
9
|
+
moreInfoUrl: 'https://docs.bachs.io/connect/guides/hosted-onboarding',
|
|
10
|
+
inputs: {
|
|
11
|
+
apiKey: parameters.BACHS_API_KEY,
|
|
12
|
+
baseUrl: parameters.BACHS_BASE_URL,
|
|
13
|
+
account: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
required: true,
|
|
16
|
+
description: 'The connected account ID.'
|
|
17
|
+
},
|
|
18
|
+
type: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
isIn: ['onboarding', 'update'],
|
|
21
|
+
defaultsTo: 'onboarding',
|
|
22
|
+
description:
|
|
23
|
+
'Use "onboarding" for new accounts and "update" to edit details.'
|
|
24
|
+
},
|
|
25
|
+
returnUrl: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
required: true,
|
|
28
|
+
description: 'Where the account lands after leaving the hosted flow.'
|
|
29
|
+
},
|
|
30
|
+
refreshUrl: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
required: true,
|
|
33
|
+
description: 'Where an expired or already used link sends the account.'
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
exits: {
|
|
37
|
+
success: {
|
|
38
|
+
description: 'The hosted link.',
|
|
39
|
+
outputVariableName: 'link',
|
|
40
|
+
outputType: 'ref'
|
|
41
|
+
},
|
|
42
|
+
couldNotCreateLink: {
|
|
43
|
+
description: 'The hosted link could not be created.',
|
|
44
|
+
outputVariableName: 'error',
|
|
45
|
+
outputType: 'ref'
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
fn: async function (inputs, exits) {
|
|
49
|
+
const adapterConfig = require('../../../adapter').config
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const link = await fetch(
|
|
53
|
+
`/accounts/${encodeURIComponent(inputs.account)}/account-links`,
|
|
54
|
+
{
|
|
55
|
+
method: 'POST',
|
|
56
|
+
apiKey: inputs.apiKey || adapterConfig.apiKey,
|
|
57
|
+
baseUrl: inputs.baseUrl || adapterConfig.baseUrl,
|
|
58
|
+
body: {
|
|
59
|
+
type: inputs.type,
|
|
60
|
+
refresh_url: inputs.refreshUrl,
|
|
61
|
+
return_url: inputs.returnUrl
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return exits.success(toLink(link))
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return exits.couldNotCreateLink(error.bachs || error)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const fetch = require('../../../helpers/fetch')
|
|
2
|
+
const parameters = require('../../../helpers/parameters')
|
|
3
|
+
const { toBalances } = require('../../../helpers/connect')
|
|
4
|
+
|
|
5
|
+
module.exports = require('machine').build({
|
|
6
|
+
friendlyName: 'Get connected account balance',
|
|
7
|
+
description:
|
|
8
|
+
'Retrieves the available and pending balance of a connected account in every currency it holds.',
|
|
9
|
+
moreInfoUrl: 'https://docs.bachs.io/connect/balances',
|
|
10
|
+
inputs: {
|
|
11
|
+
apiKey: parameters.BACHS_API_KEY,
|
|
12
|
+
baseUrl: parameters.BACHS_BASE_URL,
|
|
13
|
+
account: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
required: true,
|
|
16
|
+
description: 'The connected account ID.'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
exits: {
|
|
20
|
+
success: {
|
|
21
|
+
description: 'One entry per currency.',
|
|
22
|
+
outputVariableName: 'balances',
|
|
23
|
+
outputType: 'ref'
|
|
24
|
+
},
|
|
25
|
+
couldNotGetBalance: {
|
|
26
|
+
description: 'The balance could not be retrieved.',
|
|
27
|
+
outputVariableName: 'error',
|
|
28
|
+
outputType: 'ref'
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
fn: async function (inputs, exits) {
|
|
32
|
+
const adapterConfig = require('../../../adapter').config
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch('/balances', {
|
|
36
|
+
method: 'GET',
|
|
37
|
+
apiKey: inputs.apiKey || adapterConfig.apiKey,
|
|
38
|
+
baseUrl: inputs.baseUrl || adapterConfig.baseUrl,
|
|
39
|
+
headers: { 'X-Account-Id': inputs.account }
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return exits.success(toBalances(response))
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return exits.couldNotGetBalance(error.bachs || error)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
})
|