@spritz-finance/api-client 0.4.27 → 0.5.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/README.md +511 -1112
- package/dist/spritz-api-client.cjs +79 -85
- package/dist/spritz-api-client.d.ts +7677 -5332
- package/dist/spritz-api-client.mjs +78 -84
- package/package.json +18 -11
package/README.md
CHANGED
|
@@ -1,178 +1,128 @@
|
|
|
1
1
|
# @spritz-finance/api-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript client for the Spritz Finance API — convert crypto to fiat payments.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@spritz-finance/api-client)
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
### Using npm
|
|
10
|
-
|
|
11
9
|
```bash
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
### Using yarn
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
yarn add @spritz-finance/api-client
|
|
10
|
+
npm install @spritz-finance/api-client
|
|
11
|
+
# or
|
|
12
|
+
yarn add @spritz-finance/api-client
|
|
19
13
|
```
|
|
20
14
|
|
|
21
15
|
## Quick Start
|
|
22
16
|
|
|
23
|
-
Get started with Spritz in minutes:
|
|
24
|
-
|
|
25
17
|
```typescript
|
|
26
18
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
DebitCardNetwork,
|
|
33
|
-
AmountMode,
|
|
19
|
+
SpritzApiClient,
|
|
20
|
+
Environment,
|
|
21
|
+
PaymentNetwork,
|
|
22
|
+
BankAccountType,
|
|
23
|
+
BankAccountSubType,
|
|
34
24
|
} from '@spritz-finance/api-client'
|
|
35
25
|
|
|
36
|
-
// Initialize
|
|
26
|
+
// Initialize with your integration key
|
|
37
27
|
const client = SpritzApiClient.initialize({
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
environment: Environment.Sandbox,
|
|
29
|
+
integrationKey: 'YOUR_INTEGRATION_KEY_HERE',
|
|
40
30
|
})
|
|
41
31
|
|
|
42
|
-
// Create a user
|
|
43
|
-
const user = await client.user.create({
|
|
44
|
-
email: 'user@example.com',
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
// Set the user's API key
|
|
32
|
+
// Create a user and set their API key
|
|
33
|
+
const user = await client.user.create({ email: 'user@example.com' })
|
|
48
34
|
client.setApiKey(user.apiKey)
|
|
49
35
|
|
|
50
36
|
// Add a bank account
|
|
51
37
|
const bankAccount = await client.bankAccount.create(BankAccountType.USBankAccount, {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
38
|
+
accountNumber: '123456789',
|
|
39
|
+
routingNumber: '987654321',
|
|
40
|
+
name: 'My Checking Account',
|
|
41
|
+
ownedByUser: true,
|
|
42
|
+
subType: BankAccountSubType.Checking,
|
|
57
43
|
})
|
|
58
44
|
|
|
59
45
|
// Create a payment request
|
|
60
46
|
const paymentRequest = await client.paymentRequest.create({
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
47
|
+
amount: 100,
|
|
48
|
+
accountId: bankAccount.id,
|
|
49
|
+
network: PaymentNetwork.Ethereum,
|
|
64
50
|
})
|
|
65
51
|
|
|
66
|
-
// Get transaction data for blockchain payment
|
|
52
|
+
// Get transaction data for the blockchain payment
|
|
67
53
|
const transactionData = await client.paymentRequest.getWeb3PaymentParams({
|
|
68
|
-
|
|
69
|
-
|
|
54
|
+
paymentRequest,
|
|
55
|
+
paymentTokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC
|
|
70
56
|
})
|
|
71
57
|
|
|
72
|
-
//
|
|
58
|
+
// Execute the blockchain transaction from the user's wallet
|
|
73
59
|
```
|
|
74
60
|
|
|
75
|
-
## Table of
|
|
76
|
-
|
|
77
|
-
- [
|
|
78
|
-
- [
|
|
79
|
-
- [
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
- [
|
|
83
|
-
- [User Management](#user-management)
|
|
84
|
-
- [Creating a user](#creating-a-user-1)
|
|
85
|
-
- [Setting the User API Key](#setting-the-user-api-key)
|
|
86
|
-
- [Reauthorizing a user](#reauthorizing-a-user)
|
|
87
|
-
- [Basic User Data](#basic-user-data)
|
|
88
|
-
- [User Verification](#user-verification)
|
|
89
|
-
- [Payment Flow](#payment-flow)
|
|
90
|
-
- [Basic payment flow](#basic-payment-flow)
|
|
91
|
-
- [Note on Issuing the Blockchain Transaction](#note-on-issuing-the-blockchain-transaction)
|
|
92
|
-
- [Example](#example)
|
|
61
|
+
## Table of Contents
|
|
62
|
+
|
|
63
|
+
- [Authentication](#authentication)
|
|
64
|
+
- [Users](#users)
|
|
65
|
+
- [Creating a User](#creating-a-user)
|
|
66
|
+
- [Reauthorization](#reauthorization)
|
|
67
|
+
- [User Data](#user-data)
|
|
68
|
+
- [Identity Verification](#identity-verification)
|
|
93
69
|
- [Accounts](#accounts)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
- [
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
- [
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- [
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
- [
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
- [
|
|
117
|
-
|
|
118
|
-
- [Retrieve all onramp payments for an account](#retrieve-all-onramp-payments-for-an-account)
|
|
70
|
+
- [Bank Accounts](#bank-accounts)
|
|
71
|
+
- [Debit Cards](#debit-cards)
|
|
72
|
+
- [Bills](#bills)
|
|
73
|
+
- [Virtual Cards](#virtual-cards)
|
|
74
|
+
- [Address Book](#address-book)
|
|
75
|
+
- [Renaming Accounts](#renaming-accounts)
|
|
76
|
+
- [Deleting Accounts](#deleting-accounts)
|
|
77
|
+
- [Payments (Off-ramp)](#payments-off-ramp)
|
|
78
|
+
- [Payment Flow](#payment-flow)
|
|
79
|
+
- [Creating a Payment Request](#creating-a-payment-request)
|
|
80
|
+
- [Fulfilling a Payment — EVM](#fulfilling-a-payment--evm)
|
|
81
|
+
- [Fulfilling a Payment — Solana](#fulfilling-a-payment--solana)
|
|
82
|
+
- [Transaction Fees](#transaction-fees)
|
|
83
|
+
- [Retrieving Payments](#retrieving-payments)
|
|
84
|
+
- [Payment Limits](#payment-limits)
|
|
85
|
+
- [On-ramp](#on-ramp)
|
|
86
|
+
- [Prerequisites](#prerequisites)
|
|
87
|
+
- [Checking User Access](#checking-user-access)
|
|
88
|
+
- [Activation Steps](#activation-steps)
|
|
89
|
+
- [Virtual Accounts](#virtual-accounts)
|
|
90
|
+
- [Supported Tokens](#supported-tokens)
|
|
91
|
+
- [ACH Onramp (Direct Debit)](#ach-onramp-direct-debit)
|
|
92
|
+
- [Sandbox](#sandbox)
|
|
93
|
+
- [Bypassing KYC](#bypassing-kyc)
|
|
119
94
|
- [Webhooks](#webhooks)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
- [
|
|
123
|
-
|
|
124
|
-
- [Checking User Access Capabilities](#checking-user-access-capabilities)
|
|
125
|
-
- [Step-by-Step On-ramp Activation](#step-by-step-on-ramp-activation)
|
|
126
|
-
- [Creating Virtual Accounts](#creating-virtual-accounts)
|
|
127
|
-
- [Listing Virtual Accounts](#listing-virtual-accounts)
|
|
128
|
-
- [Supported Token Matrix](#supported-token-matrix)
|
|
129
|
-
- [Complete Example](#complete-example)
|
|
95
|
+
- [Events](#events)
|
|
96
|
+
- [Setup](#setup)
|
|
97
|
+
- [Management](#management)
|
|
98
|
+
- [Security and Signing](#security-and-signing)
|
|
130
99
|
|
|
131
|
-
##
|
|
100
|
+
## Authentication
|
|
132
101
|
|
|
133
|
-
|
|
102
|
+
Spritz uses two levels of authentication:
|
|
134
103
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
When you create a user using your integration key:
|
|
138
|
-
|
|
139
|
-
- You will receive an `API key` specific to that user.
|
|
140
|
-
- This enables you to interact with the Spritz platform on the user's behalf.
|
|
141
|
-
|
|
142
|
-
### Capabilities of the API Key
|
|
143
|
-
|
|
144
|
-
Using the user-specific API key, you can:
|
|
145
|
-
|
|
146
|
-
1. **Identity Verification**: Guide a user through the identity verification process.
|
|
147
|
-
2. **Account Addition**:
|
|
148
|
-
- Add Bills for the user.
|
|
149
|
-
- Register Bank accounts.
|
|
150
|
-
- Issue Virtual cards.
|
|
151
|
-
3. **Payment Requests**: Initiate payment requests to the aforementioned accounts.
|
|
152
|
-
4. **Blockchain Transactions**: Issue blockchain-based transactions to fulfill the payment requests.
|
|
153
|
-
5. **Payment Status**: Query the status of payments directed to the user's accounts.
|
|
154
|
-
|
|
155
|
-
## Usage
|
|
156
|
-
|
|
157
|
-
Your integration key is provided by Spritz and must always be provided.
|
|
158
|
-
The api key is specific to each user,
|
|
159
|
-
and is returned once the user is created. Leave the api key blank if you haven't created the user yet.
|
|
104
|
+
- **Integration key** — identifies your application. Provided by Spritz.
|
|
105
|
+
- **User API key** — scoped to a single user. Returned when you create a user.
|
|
160
106
|
|
|
161
107
|
```typescript
|
|
162
108
|
import { SpritzApiClient, Environment } from '@spritz-finance/api-client'
|
|
163
109
|
|
|
164
110
|
const client = SpritzApiClient.initialize({
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
111
|
+
environment: Environment.Sandbox,
|
|
112
|
+
integrationKey: 'YOUR_INTEGRATION_KEY_HERE',
|
|
113
|
+
apiKey: 'YOUR_USER_API_KEY_HERE', // omit if no user exists yet
|
|
168
114
|
})
|
|
169
115
|
```
|
|
170
116
|
|
|
171
|
-
|
|
117
|
+
After creating a user, set their API key on the client:
|
|
172
118
|
|
|
173
|
-
|
|
119
|
+
```typescript
|
|
120
|
+
client.setApiKey(user.apiKey)
|
|
121
|
+
```
|
|
174
122
|
|
|
175
|
-
|
|
123
|
+
## Users
|
|
124
|
+
|
|
125
|
+
### Creating a User
|
|
176
126
|
|
|
177
127
|
```typescript
|
|
178
128
|
const user = await client.user.create({
|
|
@@ -180,504 +130,339 @@ const user = await client.user.create({
|
|
|
180
130
|
})
|
|
181
131
|
|
|
182
132
|
// Response
|
|
183
|
-
|
|
184
|
-
email:
|
|
185
|
-
userId:
|
|
186
|
-
apiKey:
|
|
133
|
+
{
|
|
134
|
+
email: 'bilbo@shiremail.net',
|
|
135
|
+
userId: '62d17d3b377dab6c1342136e',
|
|
136
|
+
apiKey: 'ak_ZTBGDcjfdTg3NmYtZDJlZC00ZjYyLThlMDMtZmYwNDJiZDRlMWZm',
|
|
187
137
|
}
|
|
188
138
|
```
|
|
189
139
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
After creating a user, you can easily set the user's API key onto your initialized client using the provided method:
|
|
193
|
-
|
|
194
|
-
```typescript
|
|
195
|
-
client.setApiKey(user.apiKey)
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Now you're ready to issue requests on behalf of the user.
|
|
140
|
+
Creating a user with an email that already exists will throw an error.
|
|
199
141
|
|
|
200
|
-
###
|
|
142
|
+
### Reauthorization
|
|
201
143
|
|
|
202
|
-
|
|
144
|
+
If you need to recover a user's API key (e.g., the user already has a Spritz account, or you've lost access), use the OTP reauthorization flow:
|
|
203
145
|
|
|
204
146
|
```typescript
|
|
147
|
+
// Request an OTP code sent to the user's email
|
|
205
148
|
const { success } = await client.user.requestApiKey('bilbo@shiremail.net')
|
|
206
149
|
|
|
150
|
+
// Confirm with the OTP code the user provides
|
|
207
151
|
const { apiKey, userId, email } = await client.user.authorizeApiKeyWithOTP({
|
|
208
|
-
|
|
209
|
-
|
|
152
|
+
email: 'bilbo@shiremail.net',
|
|
153
|
+
otp: '123456',
|
|
210
154
|
})
|
|
211
155
|
```
|
|
212
156
|
|
|
213
|
-
###
|
|
214
|
-
|
|
215
|
-
Use this to fetch the user's basic data
|
|
157
|
+
### User Data
|
|
216
158
|
|
|
217
159
|
```typescript
|
|
218
160
|
const userData = await client.user.getCurrentUser()
|
|
219
161
|
```
|
|
220
162
|
|
|
221
|
-
###
|
|
222
|
-
|
|
223
|
-
**Purpose**: To ensure users are properly identified before interacting with the Spritz platform.
|
|
224
|
-
|
|
225
|
-
### Overview
|
|
226
|
-
|
|
227
|
-
All users must undergo basic identity verification before they can engage with the Spritz platform's features.
|
|
163
|
+
### Identity Verification
|
|
228
164
|
|
|
229
|
-
|
|
165
|
+
All users must complete identity verification before using the platform. New users start with a verification status of `NotStarted`.
|
|
230
166
|
|
|
231
|
-
|
|
167
|
+
The user's verification data is included in the `getCurrentUser` response, including verification status, verification URL, verified country, and retry capability.
|
|
232
168
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
3. **Verification Transition**: Once a user completes the identity verification process, their status will change from `NotStarted` to `Verified`. Only then can the user fully interact with the platform.
|
|
236
|
-
|
|
237
|
-
4. **Getting Verification URL**: The verification URL is included in the user data response and is essential for the user to proceed with their identity verification.
|
|
238
|
-
|
|
239
|
-
### How to Present Verification Flow to the User
|
|
240
|
-
|
|
241
|
-
Spritz offers two methods for integrating KYC verification, both using the `getVerificationParams()` method:
|
|
169
|
+
#### Getting Verification Parameters
|
|
242
170
|
|
|
243
171
|
```typescript
|
|
244
|
-
// Get verification parameters from Spritz
|
|
245
172
|
const verificationParams = await client.user.getVerificationParams()
|
|
246
173
|
|
|
247
|
-
//
|
|
174
|
+
// Returns:
|
|
248
175
|
// - inquiryId: Unique identifier for this verification inquiry
|
|
249
176
|
// - verificationUrl: URL for hosted verification
|
|
250
|
-
// - sessionToken: Token for use with
|
|
177
|
+
// - sessionToken: Token for use with Persona's Embedded Flow
|
|
251
178
|
// - verificationUrlExpiresAt: Expiration timestamp for the verification URL
|
|
252
179
|
```
|
|
253
180
|
|
|
254
|
-
####
|
|
181
|
+
#### Option 1: Verification URL
|
|
255
182
|
|
|
256
|
-
|
|
183
|
+
The simplest integration — redirect the user to the hosted verification flow:
|
|
257
184
|
|
|
258
185
|
```typescript
|
|
259
|
-
const
|
|
260
|
-
const verificationUrl = verificationParams.verificationUrl
|
|
261
|
-
const expiresAt = verificationParams.verificationUrlExpiresAt
|
|
262
|
-
|
|
263
|
-
// Important: The verification URL is short-lived and single-use
|
|
264
|
-
// - Check the expiration with verificationUrlExpiresAt before using
|
|
265
|
-
// - Once accessed, the URL becomes invalid
|
|
266
|
-
// - If verification is not completed, call getVerificationParams() again for a new URL
|
|
267
|
-
|
|
268
|
-
// Use the verification URL in your application:
|
|
269
|
-
// - Browser: Open the URL in a new browser tab
|
|
270
|
-
// - In-App: Embed the URL in an iframe within your application
|
|
271
|
-
// - Mobile: Open the URL in a native mobile web view
|
|
272
|
-
// - Email: Send users an email containing the link
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
**Note**: The verification URL can only be used once. If the user doesn't complete verification after accessing the URL, you'll need to call `getVerificationParams()` again to generate a fresh URL and session.
|
|
186
|
+
const { verificationUrl, verificationUrlExpiresAt } = await client.user.getVerificationParams()
|
|
276
187
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
```typescript
|
|
282
|
-
const verificationParams = await client.user.getVerificationParams()
|
|
283
|
-
const inquiryId = verificationParams.inquiryId
|
|
284
|
-
const sessionToken = verificationParams.sessionToken // May be null for some flows
|
|
285
|
-
|
|
286
|
-
// Use the inquiryId (and sessionToken if present) with Persona's Embedded Flow
|
|
287
|
-
// to create a custom verification experience
|
|
288
|
-
// This allows you to:
|
|
289
|
-
// - Customize the UI/UX of the verification process
|
|
290
|
-
// - Handle callbacks and events directly
|
|
291
|
-
// - Integrate verification seamlessly into your application flow
|
|
292
|
-
// - Build a native mobile experience using Persona's mobile SDKs
|
|
188
|
+
// Open in a browser tab, iframe, or mobile web view.
|
|
189
|
+
// The URL is single-use and short-lived. If it expires or the user
|
|
190
|
+
// doesn't complete verification, call getVerificationParams() again.
|
|
293
191
|
```
|
|
294
192
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
- Maintain full control over the user experience
|
|
298
|
-
- Integrate verification directly into your app without redirects
|
|
299
|
-
- Handle verification events and callbacks programmatically
|
|
300
|
-
- Track and resume verification sessions with the inquiryId
|
|
301
|
-
|
|
302
|
-
See the [Persona Embedded Flow documentation](https://docs.withpersona.com/quickstart-embedded-flow) for detailed integration instructions with the inquiryId and sessionToken.
|
|
193
|
+
#### Option 2: Embedded Flow
|
|
303
194
|
|
|
304
|
-
|
|
195
|
+
For full control over the UX, use the `inquiryId` and `sessionToken` with [Persona's Embedded Flow](https://docs.withpersona.com/quickstart-embedded-flow):
|
|
305
196
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
#### Understanding Verification Metadata
|
|
309
|
-
|
|
310
|
-
The verification metadata is available when a verification has failed and includes:
|
|
311
|
-
|
|
312
|
-
- **`failureReason`**: The specific reason why the verification failed
|
|
313
|
-
- **`details.matchedEmail`**: For duplicate identity failures, shows the email of the matched user (only if created through the same integration)
|
|
314
|
-
|
|
315
|
-
#### Verification Failure Reasons
|
|
316
|
-
|
|
317
|
-
The following verification failure reasons may be returned:
|
|
197
|
+
```typescript
|
|
198
|
+
const { inquiryId, sessionToken } = await client.user.getVerificationParams()
|
|
318
199
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
- **`kyc_check`**: KYC (Know Your Customer) check failed
|
|
323
|
-
- **`watchlist_screening`**: Watchlist screening check failed
|
|
324
|
-
- **`selfie_check`**: Selfie verification check failed
|
|
325
|
-
- **`address_invalid`**: Provided address is invalid
|
|
326
|
-
- **`duplicate_identity`**: Identity already exists in the system
|
|
200
|
+
// Use inquiryId (and sessionToken if present) with Persona's SDK
|
|
201
|
+
// to embed the verification flow directly in your app.
|
|
202
|
+
```
|
|
327
203
|
|
|
328
|
-
####
|
|
204
|
+
#### Handling Verification Failures
|
|
329
205
|
|
|
330
|
-
When
|
|
206
|
+
When verification fails, the `verificationMetadata` field on the user object provides the failure reason:
|
|
331
207
|
|
|
332
|
-
|
|
333
|
-
|
|
208
|
+
| Failure Reason | Description |
|
|
209
|
+
| -------------------------- | ---------------------------- |
|
|
210
|
+
| `verify_sms` | SMS verification failed |
|
|
211
|
+
| `documentary_verification` | Document verification failed |
|
|
212
|
+
| `risk_check` | Risk assessment failed |
|
|
213
|
+
| `kyc_check` | KYC check failed |
|
|
214
|
+
| `watchlist_screening` | Watchlist screening failed |
|
|
215
|
+
| `selfie_check` | Selfie verification failed |
|
|
216
|
+
| `address_invalid` | Invalid address |
|
|
217
|
+
| `duplicate_identity` | Identity already exists |
|
|
334
218
|
|
|
335
|
-
|
|
219
|
+
For `duplicate_identity` failures, `matchedEmail` indicates whether the duplicate was created through your integration:
|
|
336
220
|
|
|
337
221
|
```typescript
|
|
338
222
|
const userData = await client.user.getCurrentUser()
|
|
339
223
|
|
|
340
224
|
if (userData.verificationMetadata?.failureReason === 'duplicate_identity') {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
// Inform user they need to use their original Spritz account
|
|
351
|
-
}
|
|
225
|
+
const matchedEmail = userData.verificationMetadata.details.matchedEmail
|
|
226
|
+
|
|
227
|
+
if (matchedEmail) {
|
|
228
|
+
// Duplicate exists within your integration — guide user to their existing account
|
|
229
|
+
console.log(`Already verified as: ${matchedEmail}`)
|
|
230
|
+
} else {
|
|
231
|
+
// Duplicate exists in a different integration (e.g., the main Spritz app)
|
|
232
|
+
console.log('Identity already verified with another Spritz account')
|
|
233
|
+
}
|
|
352
234
|
}
|
|
353
235
|
```
|
|
354
236
|
|
|
355
|
-
## Payment Flow
|
|
356
|
-
|
|
357
|
-
### Basic payment flow
|
|
358
|
-
|
|
359
|
-
Execute a payment in a few simple steps:
|
|
360
|
-
|
|
361
|
-
1. **Select an Account**: Choose the account you wish to pay to.
|
|
362
|
-
2. **Initiate Payment Request**: Use the account's ID, your desired payment amount, and the chosen blockchain network to create a payment request.
|
|
363
|
-
3. **Retrieve Transaction Data**: Use the `getWeb3PaymentParams` method to obtain the necessary transaction data for fulfilling the payment request.
|
|
364
|
-
4. **Blockchain Transaction**: Issue the required blockchain transaction from the user's wallet.
|
|
365
|
-
5. **Payment Confirmation**: After blockchain transaction confirmation, check the status of the TradFi payment.
|
|
366
|
-
|
|
367
|
-
### Note on Issuing the Blockchain Transaction
|
|
368
|
-
|
|
369
|
-
For Spritz to process a TradFi payment to an account, we need to receive a blockchain transaction on our smart contract, which provides us the crypto funds. As an integrator, it's essential to manage how the blockchain transaction is initiated from the user's wallet to Spritz.
|
|
370
|
-
|
|
371
|
-
- **Wallet Apps**: If your application functions as a wallet, prompt the user to sign a transaction using data from `getWeb3PaymentParams`.
|
|
372
|
-
- **Web-based Dapps**: Use your existing connection to the user's wallet to prompt a transaction.
|
|
373
|
-
|
|
374
|
-
If your application doesn't have a connection to the user's wallet, consider implementing one. Some popular options include:
|
|
375
|
-
|
|
376
|
-
- [Web3Modal (Web-based)](https://github.com/WalletConnect/web3modal)
|
|
377
|
-
- [Web3Modal (React Native)](https://github.com/WalletConnect/modal-react-native)
|
|
378
|
-
- [Web3-Onboard](https://onboard.blocknative.com/docs/overview/introduction#features)
|
|
379
|
-
|
|
380
|
-
### Example
|
|
381
|
-
|
|
382
|
-
```typescript
|
|
383
|
-
// Fetch all bank accounts for the user
|
|
384
|
-
const bankAccounts = await client.bankAccount.list()
|
|
385
|
-
|
|
386
|
-
// Choose a bank account to use for the payment request
|
|
387
|
-
const account = bankAccounts[0]
|
|
388
|
-
|
|
389
|
-
// Create a payment request for the selected bank account
|
|
390
|
-
const paymentRequest = await client.paymentRequest.create({
|
|
391
|
-
amount: 100,
|
|
392
|
-
accountId: account.id,
|
|
393
|
-
network: PaymentNetwork.Ethereum,
|
|
394
|
-
})
|
|
395
|
-
|
|
396
|
-
// Retrieve the transaction data required to issue a blockchain transaction
|
|
397
|
-
const transactionData = await client.paymentRequest.getWeb3PaymentParams({
|
|
398
|
-
paymentRequest,
|
|
399
|
-
paymentTokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC on mainnet
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Issue blockchain transaction with the transaction data
|
|
404
|
-
* and wait for confirmation
|
|
405
|
-
**/
|
|
406
|
-
|
|
407
|
-
// Retrieve the payment issued for the payment request to check the payment status and confirmation
|
|
408
|
-
const payment = await client.payment.getForPaymentRequest(paymentRequest.id)
|
|
409
|
-
```
|
|
410
|
-
|
|
411
237
|
## Accounts
|
|
412
238
|
|
|
413
|
-
Spritz
|
|
414
|
-
|
|
415
|
-
### Account Types
|
|
416
|
-
|
|
417
|
-
Spritz supports four distinct types of accounts:
|
|
418
|
-
|
|
419
|
-
1. **Bank Account**
|
|
420
|
-
2. **Debit Card**
|
|
421
|
-
3. **Bill**
|
|
422
|
-
4. **Virtual Card**
|
|
423
|
-
|
|
424
|
-
Though each account type possesses its unique creation process and specific properties, it's important to understand that all of them are uniformly termed as an "account" within the Spritz platform.
|
|
425
|
-
|
|
426
|
-
### Commonalities & Differences
|
|
427
|
-
|
|
428
|
-
- **Common Properties**: Every type of account shares certain properties consistent across the platform.
|
|
429
|
-
- **Unique Properties**: Each account type also has attributes specific to its nature and functionality.
|
|
430
|
-
|
|
431
|
-
Recognizing these nuances is crucial for optimal interaction with the Spritz platform's account-related features.
|
|
239
|
+
Spritz supports four account types: **Bank Account**, **Debit Card**, **Bill**, and **Virtual Card**. All are referred to as "accounts" within the platform and share common properties (`id`, `type`, `userId`, `country`, `currency`, `createdAt`), with additional fields specific to each type.
|
|
432
240
|
|
|
433
241
|
### Bank Accounts
|
|
434
242
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
#### List user bank accounts
|
|
438
|
-
|
|
439
|
-
To retrieve all bank accounts linked to a user:
|
|
243
|
+
#### List
|
|
440
244
|
|
|
441
245
|
```typescript
|
|
442
246
|
const bankAccounts = await client.bankAccount.list()
|
|
443
247
|
```
|
|
444
248
|
|
|
445
|
-
The `bankAccount.list()` method returns an array of user-linked bank accounts, complete with essential details to display in a UI and facilitate payments:
|
|
446
|
-
|
|
447
249
|
```typescript
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
250
|
+
// Example response
|
|
251
|
+
;[
|
|
252
|
+
{
|
|
253
|
+
id: '62d17d3b377dab6c1342136e',
|
|
254
|
+
name: 'Precious Savings',
|
|
255
|
+
type: 'BankAccount',
|
|
256
|
+
bankAccountType: 'USBankAccount',
|
|
257
|
+
bankAccountSubType: 'Checking',
|
|
258
|
+
userId: '62d17d3b377dab6c1342136e',
|
|
259
|
+
accountNumber: '1234567',
|
|
260
|
+
bankAccountDetails: {
|
|
261
|
+
routingNumber: '00000123',
|
|
262
|
+
},
|
|
263
|
+
country: 'US',
|
|
264
|
+
currency: 'USD',
|
|
265
|
+
email: 'bilbo@shiremail.net',
|
|
266
|
+
institution: {
|
|
267
|
+
id: '62d27d4b277dab3c1342126e',
|
|
268
|
+
name: 'Shire Bank',
|
|
269
|
+
logo: 'https://tinyurl.com/shire-bank-logo',
|
|
270
|
+
},
|
|
271
|
+
ownedByUser: true,
|
|
272
|
+
createdAt: '2023-05-03T11:25:02.401Z',
|
|
273
|
+
deliveryMethods: ['STANDARD', 'INSTANT'],
|
|
274
|
+
},
|
|
275
|
+
]
|
|
471
276
|
```
|
|
472
277
|
|
|
473
|
-
####
|
|
474
|
-
|
|
475
|
-
Currently, Spritz supports the addition of US bank accounts:
|
|
476
|
-
|
|
477
|
-
The input structure for adding a US bank account is defined as:
|
|
478
|
-
|
|
479
|
-
```typescript
|
|
480
|
-
// Input arguments for creating a US bank account
|
|
481
|
-
export interface USBankAccountInput {
|
|
482
|
-
accountNumber: string
|
|
483
|
-
email?: string | null
|
|
484
|
-
name?: string | null
|
|
485
|
-
ownedByUser?: boolean | null
|
|
486
|
-
routingNumber: string
|
|
487
|
-
subType: BankAccountSubType
|
|
488
|
-
}
|
|
489
|
-
```
|
|
278
|
+
#### Create US Bank Account
|
|
490
279
|
|
|
491
280
|
```typescript
|
|
492
281
|
import { BankAccountType, BankAccountSubType } from '@spritz-finance/api-client'
|
|
493
282
|
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
283
|
+
const bankAccount = await client.bankAccount.create(BankAccountType.USBankAccount, {
|
|
284
|
+
accountNumber: '123456789',
|
|
285
|
+
routingNumber: '987654321',
|
|
286
|
+
name: 'Precious Savings',
|
|
287
|
+
ownedByUser: true,
|
|
288
|
+
subType: BankAccountSubType.Savings,
|
|
500
289
|
})
|
|
501
290
|
```
|
|
502
291
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
Currently, Spritz supports the addition of Canadian bank accounts:
|
|
506
|
-
|
|
507
|
-
The input structure for adding a Canadian bank account is defined as:
|
|
292
|
+
Input fields:
|
|
508
293
|
|
|
509
294
|
```typescript
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
institutionNumber: string
|
|
518
|
-
subType: BankAccountSubType
|
|
295
|
+
interface USBankAccountInput {
|
|
296
|
+
accountNumber: string
|
|
297
|
+
routingNumber: string
|
|
298
|
+
subType: BankAccountSubType
|
|
299
|
+
name?: string | null
|
|
300
|
+
email?: string | null
|
|
301
|
+
ownedByUser?: boolean | null
|
|
519
302
|
}
|
|
520
303
|
```
|
|
521
304
|
|
|
305
|
+
#### Create Canadian Bank Account
|
|
306
|
+
|
|
522
307
|
```typescript
|
|
523
308
|
import { BankAccountType, BankAccountSubType } from '@spritz-finance/api-client'
|
|
524
309
|
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
310
|
+
const bankAccount = await client.bankAccount.create(BankAccountType.CABankAccount, {
|
|
311
|
+
accountNumber: '123456789',
|
|
312
|
+
transitNumber: '12345',
|
|
313
|
+
institutionNumber: '123',
|
|
314
|
+
name: 'Precious Savings',
|
|
315
|
+
ownedByUser: true,
|
|
316
|
+
subType: BankAccountSubType.Savings,
|
|
532
317
|
})
|
|
533
318
|
```
|
|
534
319
|
|
|
535
|
-
|
|
320
|
+
Input fields:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
interface CABankAccountInput {
|
|
324
|
+
accountNumber: string
|
|
325
|
+
transitNumber: string
|
|
326
|
+
institutionNumber: string
|
|
327
|
+
name: string
|
|
328
|
+
subType: BankAccountSubType
|
|
329
|
+
email?: string
|
|
330
|
+
ownedByUser?: boolean | null
|
|
331
|
+
}
|
|
332
|
+
```
|
|
536
333
|
|
|
537
|
-
|
|
334
|
+
### Debit Cards
|
|
538
335
|
|
|
539
|
-
|
|
336
|
+
Supported networks: **Visa** and **Mastercard**.
|
|
540
337
|
|
|
541
|
-
|
|
338
|
+
#### List
|
|
542
339
|
|
|
543
340
|
```typescript
|
|
544
341
|
const debitCards = await client.debitCard.list()
|
|
545
342
|
```
|
|
546
343
|
|
|
547
|
-
The `debitCard.list()` method returns an array of user-linked debit cards:
|
|
548
|
-
|
|
549
344
|
```typescript
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
345
|
+
// Example response
|
|
346
|
+
;[
|
|
347
|
+
{
|
|
348
|
+
id: '62d17d3b377dab6c1342136e',
|
|
349
|
+
type: 'DebitCard',
|
|
350
|
+
name: 'My Visa Debit',
|
|
351
|
+
userId: '62d17d3b377dab6c1342136e',
|
|
352
|
+
country: 'US',
|
|
353
|
+
currency: 'USD',
|
|
354
|
+
payable: true,
|
|
355
|
+
debitCardNetwork: 'Visa',
|
|
356
|
+
expirationDate: '12/25',
|
|
357
|
+
cardNumber: '4111111111111111',
|
|
358
|
+
mask: '1111',
|
|
359
|
+
createdAt: '2023-01-01T00:00:00Z',
|
|
360
|
+
paymentCount: 5,
|
|
361
|
+
externalId: 'ext-123',
|
|
362
|
+
},
|
|
567
363
|
]
|
|
568
364
|
```
|
|
569
365
|
|
|
570
|
-
####
|
|
571
|
-
|
|
572
|
-
To add a new debit card for a user:
|
|
366
|
+
#### Create
|
|
573
367
|
|
|
574
368
|
```typescript
|
|
575
|
-
import { DebitCardNetwork } from '@spritz-finance/api-client'
|
|
576
|
-
|
|
577
369
|
const debitCard = await client.debitCard.create({
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
370
|
+
cardNumber: '4111111111111111', // 13-19 digits
|
|
371
|
+
expirationDate: '12/25', // MM/YY
|
|
372
|
+
name: 'My Visa Debit', // optional
|
|
581
373
|
})
|
|
582
374
|
```
|
|
583
375
|
|
|
584
|
-
The input structure for adding a debit card is:
|
|
585
|
-
|
|
586
|
-
```typescript
|
|
587
|
-
export interface DebitCardInput {
|
|
588
|
-
name?: string | null // Optional name for the card
|
|
589
|
-
cardNumber: string // Full card number (13-19 digits)
|
|
590
|
-
expirationDate: string // Expiration date in MM/YY format
|
|
591
|
-
}
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
Supported debit card networks:
|
|
595
|
-
|
|
596
|
-
- `Visa`
|
|
597
|
-
- `Mastercard`
|
|
598
|
-
|
|
599
376
|
### Bills
|
|
600
377
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
#### List user bills
|
|
604
|
-
|
|
605
|
-
To retrieve all bill accounts associated with a user:
|
|
378
|
+
#### List
|
|
606
379
|
|
|
607
380
|
```typescript
|
|
608
381
|
const bills = await client.bill.list()
|
|
609
382
|
```
|
|
610
383
|
|
|
611
|
-
The `bill.list()` method returns an array of user-associated bills, complete with essential details for display in a UI and for processing payments:
|
|
612
|
-
|
|
613
384
|
```typescript
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
385
|
+
// Example response
|
|
386
|
+
;[
|
|
387
|
+
{
|
|
388
|
+
id: '62d17d3b377dab6c1342136e',
|
|
389
|
+
name: 'Precious Credit Card',
|
|
390
|
+
type: 'Bill',
|
|
391
|
+
billType: 'CreditCard',
|
|
392
|
+
userId: '62d17d3b377dab6c1342136e',
|
|
393
|
+
mask: '4567',
|
|
394
|
+
originator: 'User',
|
|
395
|
+
payable: true,
|
|
396
|
+
verifying: false,
|
|
397
|
+
billAccountDetails: {
|
|
398
|
+
balance: 240.23,
|
|
399
|
+
amountDue: 28.34,
|
|
400
|
+
openedAt: '2023-05-03T11:25:02.401Z',
|
|
401
|
+
lastPaymentAmount: null,
|
|
402
|
+
lastPaymentDate: null,
|
|
403
|
+
nextPaymentDueDate: '2023-06-03T11:25:02.401Z',
|
|
404
|
+
nextPaymentMinimumAmount: 28.34,
|
|
405
|
+
lastStatementBalance: 180.23,
|
|
406
|
+
remainingStatementBalance: null,
|
|
407
|
+
},
|
|
408
|
+
country: 'US',
|
|
409
|
+
currency: 'USD',
|
|
410
|
+
dataSync: {
|
|
411
|
+
lastSync: '2023-05-03T11:25:02.401Z',
|
|
412
|
+
syncStatus: 'Active',
|
|
413
|
+
},
|
|
414
|
+
institution: {
|
|
415
|
+
id: '62d27d4b277dab3c1342126e',
|
|
416
|
+
name: 'Shire Bank Credit Card',
|
|
417
|
+
logo: 'https://tinyurl.com/shire-bank-logo',
|
|
418
|
+
},
|
|
419
|
+
createdAt: '2023-05-03T11:25:02.401Z',
|
|
420
|
+
deliveryMethods: ['STANDARD'],
|
|
646
421
|
},
|
|
647
|
-
createdAt: '2023-05-03T11:25:02.401Z',
|
|
648
|
-
deliveryMethods: ['STANDARD'],
|
|
649
|
-
},
|
|
650
422
|
]
|
|
651
423
|
```
|
|
652
424
|
|
|
653
|
-
####
|
|
425
|
+
#### Create
|
|
654
426
|
|
|
655
|
-
|
|
427
|
+
Adding a bill requires the institution ID and the account number:
|
|
656
428
|
|
|
657
429
|
```typescript
|
|
658
430
|
import { BillType } from '@spritz-finance/api-client'
|
|
659
431
|
|
|
660
432
|
const institutions = await client.institution.popularUSBillInstitutions(BillType.CreditCard)
|
|
661
|
-
const
|
|
662
|
-
|
|
433
|
+
const bill = await client.bill.create(institutions[0].id, '12345678913213', BillType.CreditCard)
|
|
434
|
+
```
|
|
663
435
|
|
|
664
|
-
|
|
436
|
+
#### Finding Bill Institutions
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
// Popular institutions (optionally filtered by bill type)
|
|
440
|
+
const popular = await client.institution.popularUSBillInstitutions()
|
|
441
|
+
const mortgages = await client.institution.popularUSBillInstitutions(BillType.Mortgage)
|
|
442
|
+
|
|
443
|
+
// Search by name
|
|
444
|
+
const results = await client.institution.searchUSBillInstitutions('american express')
|
|
445
|
+
const filtered = await client.institution.searchUSBillInstitutions(
|
|
446
|
+
'american express',
|
|
447
|
+
BillType.CreditCard
|
|
448
|
+
)
|
|
665
449
|
```
|
|
666
450
|
|
|
667
|
-
### Virtual
|
|
451
|
+
### Virtual Cards
|
|
668
452
|
|
|
669
|
-
|
|
453
|
+
Virtual cards are crypto-funded payment cards.
|
|
670
454
|
|
|
671
|
-
#### Fetch
|
|
455
|
+
#### Fetch
|
|
672
456
|
|
|
673
|
-
|
|
457
|
+
Returns card details excluding sensitive fields (card number, CVV):
|
|
674
458
|
|
|
675
459
|
```typescript
|
|
676
460
|
const virtualCard = await client.virtualCard.fetch()
|
|
677
461
|
```
|
|
678
462
|
|
|
679
463
|
```typescript
|
|
680
|
-
|
|
464
|
+
// Example response
|
|
465
|
+
{
|
|
681
466
|
id: '62d17d3b377dab6c1342136e',
|
|
682
467
|
type: 'VirtualCard',
|
|
683
468
|
virtualCardType: 'USVirtualDebitCard',
|
|
@@ -703,7 +488,7 @@ const virtualCard = {
|
|
|
703
488
|
}
|
|
704
489
|
```
|
|
705
490
|
|
|
706
|
-
#### Create
|
|
491
|
+
#### Create
|
|
707
492
|
|
|
708
493
|
```typescript
|
|
709
494
|
import { VirtualCardType } from '@spritz-finance/api-client'
|
|
@@ -711,189 +496,71 @@ import { VirtualCardType } from '@spritz-finance/api-client'
|
|
|
711
496
|
const virtualCard = await client.virtualCard.create(VirtualCardType.USVirtualDebitCard)
|
|
712
497
|
```
|
|
713
498
|
|
|
714
|
-
#### Displaying
|
|
715
|
-
|
|
716
|
-
To show the sensitive card details that users require for payment transactions, you must integrate our dedicated drop-in widget. This widget securely renders card details. Use the renderSecret, obtained from the standard fetch card endpoint, in conjunction with the user's API key.
|
|
499
|
+
#### Displaying Sensitive Card Details
|
|
717
500
|
|
|
718
|
-
|
|
501
|
+
To render the full card number and CVV, use the `renderSecret` from the fetch response with one of the Spritz secure element libraries:
|
|
719
502
|
|
|
720
|
-
- [React
|
|
721
|
-
- [React Native
|
|
503
|
+
- [React](https://www.npmjs.com/package/@spritz-finance/react-secure-elements)
|
|
504
|
+
- [React Native](https://www.npmjs.com/package/@spritz-finance/react-native-secure-elements)
|
|
722
505
|
|
|
723
|
-
|
|
506
|
+
### Address Book
|
|
724
507
|
|
|
725
|
-
Each account
|
|
508
|
+
Each account is allocated a unique on-chain payment address per network. Tokens sent to these addresses are automatically credited to the account. Accepted tokens vary by network — generally USDC and USDT at minimum.
|
|
726
509
|
|
|
727
510
|
```typescript
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
paymentAddresses: [
|
|
736
|
-
{
|
|
737
|
-
network: 'ethereum',
|
|
738
|
-
address: '0xc0ffee254729296a45a3885639AC7E10F9d54979',
|
|
739
|
-
},
|
|
740
|
-
{
|
|
741
|
-
network: 'polygon',
|
|
742
|
-
address: '0xc0ffee254729296a45a3885639AC7E10F9d54979',
|
|
743
|
-
},
|
|
744
|
-
],
|
|
745
|
-
},
|
|
746
|
-
]
|
|
747
|
-
```
|
|
748
|
-
|
|
749
|
-
## Account Management
|
|
750
|
-
|
|
751
|
-
### Renaming accounts
|
|
752
|
-
|
|
753
|
-
#### Rename a bank account
|
|
754
|
-
|
|
755
|
-
You can conveniently change the display name of a bank account using the following endpoint. The first argument specifies the ID of the bank account, while the second argument represents the desired new name for the account.
|
|
756
|
-
|
|
757
|
-
```typescript
|
|
758
|
-
const updateAccount = await client.bankAccount.rename('62d17d3b377dab6c1342136e', 'My new account')
|
|
759
|
-
```
|
|
760
|
-
|
|
761
|
-
#### Rename a debit card
|
|
762
|
-
|
|
763
|
-
You can change the display name of a debit card:
|
|
764
|
-
|
|
765
|
-
```typescript
|
|
766
|
-
const updatedCard = await client.debitCard.rename(
|
|
767
|
-
'62d17d3b377dab6c1342136e',
|
|
768
|
-
'My primary debit card'
|
|
769
|
-
)
|
|
770
|
-
```
|
|
771
|
-
|
|
772
|
-
#### Rename a bill
|
|
773
|
-
|
|
774
|
-
You can conveniently change the display name of a bill using the following endpoint. The first argument specifies the ID of the bill, while the second argument represents the desired new name for the account.
|
|
775
|
-
|
|
776
|
-
```typescript
|
|
777
|
-
const updateAccount = await client.bill.rename('62d17d3b377dab6c1342136e', 'My first credit card')
|
|
511
|
+
// Included in account responses
|
|
512
|
+
{
|
|
513
|
+
paymentAddresses: [
|
|
514
|
+
{ network: 'ethereum', address: '0xc0ffee254729296a45a3885639AC7E10F9d54979' },
|
|
515
|
+
{ network: 'polygon', address: '0xc0ffee254729296a45a3885639AC7E10F9d54979' },
|
|
516
|
+
],
|
|
517
|
+
}
|
|
778
518
|
```
|
|
779
519
|
|
|
780
|
-
###
|
|
781
|
-
|
|
782
|
-
#### Delete a bank account
|
|
783
|
-
|
|
784
|
-
To remove a bank account from a user's account, you can use the following endpoint. You only need to specify the ID of the bank account that you want to delete as an argument.
|
|
520
|
+
### Renaming Accounts
|
|
785
521
|
|
|
786
522
|
```typescript
|
|
787
|
-
await client.bankAccount.
|
|
523
|
+
await client.bankAccount.rename('account-id', 'New Name')
|
|
524
|
+
await client.debitCard.rename('card-id', 'New Name')
|
|
525
|
+
await client.bill.rename('bill-id', 'New Name')
|
|
788
526
|
```
|
|
789
527
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
To remove a debit card from a user's account:
|
|
528
|
+
### Deleting Accounts
|
|
793
529
|
|
|
794
530
|
```typescript
|
|
795
|
-
await client.
|
|
531
|
+
await client.bankAccount.delete('account-id')
|
|
532
|
+
await client.debitCard.delete('card-id')
|
|
533
|
+
await client.bill.delete('bill-id')
|
|
796
534
|
```
|
|
797
535
|
|
|
798
|
-
|
|
536
|
+
## Payments (Off-ramp)
|
|
799
537
|
|
|
800
|
-
|
|
538
|
+
### Payment Flow
|
|
801
539
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
When adding a new bill for a user, we need to provide a reference to the institution who holds the account for the user. As an example, if a user wanted to add their Chase Visa Credit Card to their Spritz account, the Institution of the account would be `Chase Credit Cards` and then the account number provided would be the 16-digit card number for their credit card.
|
|
540
|
+
1. **Select an account** — choose the bank account, debit card, or bill to pay.
|
|
541
|
+
2. **Create a payment request** — specify amount, account ID, and blockchain network.
|
|
542
|
+
3. **Get transaction data** — call `getWeb3PaymentParams` (EVM) or `getSolanaPaymentParams` (Solana).
|
|
543
|
+
4. **Execute the blockchain transaction** — sign and submit from the user's wallet.
|
|
544
|
+
5. **Check payment status** — query the resulting fiat payment.
|
|
809
545
|
|
|
810
|
-
|
|
546
|
+
> Your application needs a connection to the user's wallet to sign transactions. If you don't have one, consider [Web3Modal](https://github.com/WalletConnect/web3modal) or [Web3-Onboard](https://onboard.blocknative.com/docs/overview/introduction#features).
|
|
811
547
|
|
|
812
|
-
###
|
|
548
|
+
### Creating a Payment Request
|
|
813
549
|
|
|
814
550
|
```typescript
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
// Optionally filter by a specific bill type
|
|
818
|
-
const popularInstitutions = await client.institution.popularUSBillInstitutions(BillType.Mortgage)
|
|
819
|
-
```
|
|
820
|
-
|
|
821
|
-
### Searching for bill institutions by name
|
|
551
|
+
import { PaymentNetwork, AmountMode } from '@spritz-finance/api-client'
|
|
822
552
|
|
|
823
|
-
```typescript
|
|
824
|
-
const institutions = await client.institution.searchUSBillInstitutions('american express')
|
|
825
|
-
|
|
826
|
-
// Optionally filter by a specific bill type
|
|
827
|
-
const institutions = await client.institution.searchUSBillInstitutions(
|
|
828
|
-
'american express',
|
|
829
|
-
BillType.CreditCard
|
|
830
|
-
)
|
|
831
|
-
```
|
|
832
|
-
|
|
833
|
-
## Payment Requests
|
|
834
|
-
|
|
835
|
-
A payment request refers to the intent to initiate a payment to a specific account. Once a payment request is created, a blockchain transaction is required to fulfill it. After the blockchain transaction settles, the payment request is completed, and a fiat payment is issued to the designated account.
|
|
836
|
-
|
|
837
|
-
### Create a payment request
|
|
838
|
-
|
|
839
|
-
To initiate a payment request for a specific account, you can use the following functionality. The required inputs for creating a payment request include the ID of the account to be paid, the amount of the fiat payment in USD, and the network on which the blockchain transaction will settle.
|
|
840
|
-
|
|
841
|
-
#### Amount Mode
|
|
842
|
-
|
|
843
|
-
When creating a payment request, you can specify how the amount should be calculated using the `amountMode` parameter:
|
|
844
|
-
|
|
845
|
-
- **`TOTAL_AMOUNT`**: The payer covers the entire amount including fees. The specified amount is the total that will be deducted from the payer's wallet.
|
|
846
|
-
- **`AMOUNT_RECEIVED`** (default): The payment that arrives in the bank account excludes fees. The specified amount is what the recipient will receive, and fees are added on top.
|
|
847
|
-
|
|
848
|
-
This allows you to control whether fees are included in or added to the specified amount.
|
|
849
|
-
|
|
850
|
-
#### Fee Subsidies (Integrator Feature)
|
|
851
|
-
|
|
852
|
-
Integrators can subsidize transaction fees on behalf of their users. When enabled, you can cover part or all of the transaction fees, which Spritz will invoice to you separately.
|
|
853
|
-
|
|
854
|
-
> **Note**: Fee subsidies are a gated feature and must be enabled by the Spritz team before use. Contact Spritz to request access.
|
|
855
|
-
|
|
856
|
-
**Parameters:**
|
|
857
|
-
|
|
858
|
-
- **`feeSubsidyPercentage`** (string): The percentage of the transaction fee you want to subsidize for the user. For example, `"100"` covers the entire fee, `"50"` covers half.
|
|
859
|
-
- **`maxFeeSubsidyAmount`** (string, optional): The maximum dollar amount you're willing to subsidize per transaction. If the fee exceeds this cap, the user pays the difference.
|
|
860
|
-
|
|
861
|
-
**How it works:**
|
|
862
|
-
|
|
863
|
-
1. Set `feeSubsidyPercentage` to define what portion of fees you'll cover
|
|
864
|
-
2. Optionally set `maxFeeSubsidyAmount` to cap your exposure
|
|
865
|
-
3. Spritz invoices you separately for the subsidized amounts
|
|
866
|
-
4. Users see reduced or zero fees on their transactions
|
|
867
|
-
|
|
868
|
-
**Example:**
|
|
869
|
-
|
|
870
|
-
```typescript
|
|
871
|
-
// Cover 100% of fees up to $5 per transaction
|
|
872
553
|
const paymentRequest = await client.paymentRequest.create({
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
554
|
+
amount: 100,
|
|
555
|
+
accountId: account.id,
|
|
556
|
+
network: PaymentNetwork.Ethereum,
|
|
557
|
+
deliveryMethod: 'INSTANT', // optional
|
|
558
|
+
amountMode: AmountMode.TOTAL_AMOUNT, // optional, defaults to AMOUNT_RECEIVED
|
|
878
559
|
})
|
|
879
|
-
|
|
880
|
-
// If transaction fee is $3: integrator pays $3, user pays $0
|
|
881
|
-
// If transaction fee is $8: integrator pays $5, user pays $3
|
|
882
560
|
```
|
|
883
561
|
|
|
884
562
|
```typescript
|
|
885
|
-
import {PaymentNetwork, AmountMode} from '@spritz-finance/api-client';
|
|
886
|
-
|
|
887
|
-
const paymentRequest = await client.paymentRequest.create({
|
|
888
|
-
amount: 100,
|
|
889
|
-
accountId: account.id,
|
|
890
|
-
network: PaymentNetwork.Ethereum,
|
|
891
|
-
deliveryMethod: 'INSTANT',
|
|
892
|
-
amountMode: AmountMode.TOTAL_AMOUNT // Optional, defaults to AMOUNT_RECEIVED
|
|
893
|
-
});
|
|
894
|
-
|
|
895
563
|
// Example response
|
|
896
|
-
|
|
897
564
|
{
|
|
898
565
|
id: '645399c8c1ac408007b12273',
|
|
899
566
|
userId: '63d12d3B577fab6c6382136e',
|
|
@@ -903,108 +570,98 @@ const paymentRequest = await client.paymentRequest.create({
|
|
|
903
570
|
feeAmount: 0,
|
|
904
571
|
amountDue: 100,
|
|
905
572
|
network: 'ethereum',
|
|
906
|
-
createdAt: '2023-05-04T11:40:56.488Z'
|
|
573
|
+
createdAt: '2023-05-04T11:40:56.488Z',
|
|
907
574
|
}
|
|
908
575
|
```
|
|
909
576
|
|
|
910
|
-
|
|
577
|
+
#### Amount Mode
|
|
911
578
|
|
|
912
|
-
|
|
579
|
+
- **`AMOUNT_RECEIVED`** (default) — the recipient receives the specified amount; fees are added on top.
|
|
580
|
+
- **`TOTAL_AMOUNT`** — the specified amount includes fees; the recipient receives less.
|
|
913
581
|
|
|
914
|
-
|
|
582
|
+
#### Fee Subsidies
|
|
915
583
|
|
|
916
|
-
|
|
917
|
-
import {PaymentNetwork} from '@spritz-finance/api-client';
|
|
584
|
+
Integrators can subsidize transaction fees on behalf of users. This is a gated feature — contact Spritz to enable it.
|
|
918
585
|
|
|
586
|
+
```typescript
|
|
919
587
|
const paymentRequest = await client.paymentRequest.create({
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
588
|
+
amount: 100,
|
|
589
|
+
accountId: account.id,
|
|
590
|
+
network: PaymentNetwork.Ethereum,
|
|
591
|
+
feeSubsidyPercentage: '100', // percentage of fee to cover
|
|
592
|
+
maxFeeSubsidyAmount: '5', // cap per transaction in USD
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
// Fee = $3 → integrator pays $3, user pays $0
|
|
596
|
+
// Fee = $8 → integrator pays $5, user pays $3
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
Subsidized amounts are invoiced to the integrator separately.
|
|
924
600
|
|
|
601
|
+
### Fulfilling a Payment — EVM
|
|
602
|
+
|
|
603
|
+
For EVM networks, you interact with the SpritzPay smart contract ([deployment addresses](https://docs.spritz.finance/docs/deployment-addresses)):
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
925
606
|
const transactionData = await client.paymentRequest.getWeb3PaymentParams({
|
|
926
|
-
|
|
927
|
-
|
|
607
|
+
paymentRequest,
|
|
608
|
+
paymentTokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC
|
|
928
609
|
})
|
|
929
610
|
|
|
930
611
|
// Example response
|
|
931
|
-
|
|
932
612
|
{
|
|
933
613
|
contractAddress: '0xbF7Abc15f00a8C2d6b13A952c58d12b7c194A8D0',
|
|
934
614
|
method: 'payWithToken',
|
|
935
|
-
calldata: '
|
|
615
|
+
calldata: '0xd71d9632...',
|
|
936
616
|
value: null,
|
|
937
617
|
requiredTokenInput: '100000000',
|
|
938
618
|
}
|
|
939
619
|
```
|
|
940
620
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
### Fulfil a payment request (Solana transactions)
|
|
621
|
+
Use `contractAddress` as `to`, `calldata` as `data`, and `value` to build the transaction. Check `requiredTokenInput` against the user's balance before submitting.
|
|
944
622
|
|
|
945
|
-
|
|
623
|
+
### Fulfilling a Payment — Solana
|
|
946
624
|
|
|
947
625
|
```typescript
|
|
948
|
-
import {PaymentNetwork} from '@spritz-finance/api-client';
|
|
949
|
-
|
|
950
|
-
const paymentRequest = await client.paymentRequest.create({
|
|
951
|
-
amount: 100,
|
|
952
|
-
accountId: account.id,
|
|
953
|
-
network: PaymentNetwork.Solana,
|
|
954
|
-
});
|
|
955
|
-
|
|
956
626
|
const transactionData = await client.paymentRequest.getSolanaPaymentParams({
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
627
|
+
paymentRequest,
|
|
628
|
+
paymentTokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
|
|
629
|
+
signer: 'YourSolanaWalletAddress',
|
|
960
630
|
})
|
|
961
631
|
|
|
962
632
|
// Example response
|
|
963
|
-
|
|
964
633
|
{
|
|
965
|
-
versionedTransaction: VersionedTransaction, //
|
|
966
|
-
transactionSerialized: '
|
|
634
|
+
versionedTransaction: VersionedTransaction, // ready to sign
|
|
635
|
+
transactionSerialized: 'base64...', // base64-encoded alternative
|
|
967
636
|
}
|
|
968
637
|
```
|
|
969
638
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
### Transaction fees
|
|
639
|
+
### Transaction Fees
|
|
973
640
|
|
|
974
|
-
|
|
641
|
+
Fees apply once monthly volume exceeds $100. To check the fee for a given amount:
|
|
975
642
|
|
|
976
643
|
```typescript
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
// Example response
|
|
980
|
-
0.01
|
|
644
|
+
const fee = await client.paymentRequest.transactionPrice(101)
|
|
645
|
+
// Returns: 0.01
|
|
981
646
|
```
|
|
982
647
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
Payments represent a fiat payment that has been issued to the account. Once the status of the Payment Request has moved to `Confirmed` then the Payment will be created.
|
|
986
|
-
|
|
987
|
-
### Transaction Details
|
|
988
|
-
|
|
989
|
-
Payments now include transaction details about the blockchain transaction that fulfilled the payment. When a payment is completed, the `transaction` field contains:
|
|
648
|
+
### Retrieving Payments
|
|
990
649
|
|
|
991
|
-
|
|
992
|
-
- **from**: The wallet address that sent the payment
|
|
993
|
-
- **asset**: The token contract address used for payment
|
|
994
|
-
- **value**: The amount transferred (in the token's smallest unit)
|
|
995
|
-
- **network**: The blockchain network used (e.g., 'ethereum', 'polygon', etc.)
|
|
650
|
+
Payments are created once a payment request reaches `Confirmed` status.
|
|
996
651
|
|
|
997
|
-
|
|
652
|
+
```typescript
|
|
653
|
+
// By payment ID
|
|
654
|
+
const payment = await client.payment.fetchById('6368e3a3ec516e9572bbd23b')
|
|
998
655
|
|
|
999
|
-
|
|
656
|
+
// By payment request ID
|
|
657
|
+
const payment = await client.payment.getForPaymentRequest(paymentRequest.id)
|
|
1000
658
|
|
|
1001
|
-
|
|
659
|
+
// All payments for an account
|
|
660
|
+
const payments = await client.payment.listForAccount(account.id)
|
|
661
|
+
```
|
|
1002
662
|
|
|
1003
663
|
```typescript
|
|
1004
|
-
const payment = await client.payment.fetchById('6368e3a3ec516e9572bbd23b');
|
|
1005
|
-
|
|
1006
664
|
// Example response
|
|
1007
|
-
|
|
1008
665
|
{
|
|
1009
666
|
id: '6368e3a3ec516e9572bbd23b',
|
|
1010
667
|
userId: '63d12d3B577fab6c6382136e',
|
|
@@ -1014,513 +671,255 @@ const payment = await client.payment.fetchById('6368e3a3ec516e9572bbd23b');
|
|
|
1014
671
|
feeAmount: null,
|
|
1015
672
|
createdAt: '2022-11-07T10:53:23.998Z',
|
|
1016
673
|
transaction: {
|
|
1017
|
-
hash: '
|
|
674
|
+
hash: '0x1234...abcdef',
|
|
1018
675
|
from: '0xYourWalletAddress',
|
|
1019
676
|
asset: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
|
1020
677
|
value: 100000000,
|
|
1021
|
-
network: 'ethereum'
|
|
1022
|
-
}
|
|
678
|
+
network: 'ethereum',
|
|
679
|
+
},
|
|
1023
680
|
}
|
|
1024
681
|
```
|
|
1025
682
|
|
|
1026
|
-
###
|
|
683
|
+
### Payment Limits
|
|
1027
684
|
|
|
1028
685
|
```typescript
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
const paymentRequest = await client.paymentRequest.create({
|
|
1032
|
-
amount: 100,
|
|
1033
|
-
accountId: account.id,
|
|
1034
|
-
network: PaymentNetwork.Ethereum,
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
const payment = await client.payment.getForPaymentRequest(paymentRequest.id);
|
|
686
|
+
const limits = await client.payment.getPaymentLimits(account.id)
|
|
1038
687
|
|
|
1039
688
|
// Example response
|
|
1040
|
-
|
|
1041
689
|
{
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
status: 'PENDING',
|
|
1045
|
-
accountId: '6322445f10d3f4d19c4d72fe',
|
|
1046
|
-
amount: 100,
|
|
1047
|
-
feeAmount: null,
|
|
1048
|
-
createdAt: '2022-11-07T10:53:23.998Z',
|
|
1049
|
-
transaction: null // Will be populated once the payment is fulfilled
|
|
690
|
+
perTransaction: 20000,
|
|
691
|
+
dailyRemainingVolume: 150000,
|
|
1050
692
|
}
|
|
1051
|
-
|
|
1052
693
|
```
|
|
1053
694
|
|
|
1054
|
-
|
|
695
|
+
## On-ramp
|
|
1055
696
|
|
|
1056
|
-
|
|
1057
|
-
const payments = await client.payment.listForAccount(account.id)
|
|
697
|
+
The on-ramp feature allows users to purchase crypto stablecoins via ACH or wire transfer.
|
|
1058
698
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
[
|
|
1062
|
-
{
|
|
1063
|
-
id: '6368e3a3ec516e9572bbd23b',
|
|
1064
|
-
userId: '63d12d3B577fab6c6382136e',
|
|
1065
|
-
status: 'COMPLETED',
|
|
1066
|
-
accountId: '6322445f10d3f4d19c4d72fe',
|
|
1067
|
-
amount: 100,
|
|
1068
|
-
feeAmount: null,
|
|
1069
|
-
createdAt: '2022-11-07T10:53:23.998Z',
|
|
1070
|
-
transaction: {
|
|
1071
|
-
__typename: 'BlockchainTransaction',
|
|
1072
|
-
hash: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
|
|
1073
|
-
from: '0xYourWalletAddress',
|
|
1074
|
-
asset: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
|
1075
|
-
value: 100000000,
|
|
1076
|
-
network: 'ethereum'
|
|
1077
|
-
}
|
|
1078
|
-
},
|
|
1079
|
-
]
|
|
1080
|
-
```
|
|
699
|
+
### Prerequisites
|
|
1081
700
|
|
|
1082
|
-
|
|
701
|
+
1. Complete platform-level KYC (identity verification)
|
|
702
|
+
2. Accept the third-party on-ramp provider's Terms of Service
|
|
703
|
+
3. Provider KYC processes automatically after ToS acceptance
|
|
1083
704
|
|
|
1084
|
-
|
|
705
|
+
### Checking User Access
|
|
1085
706
|
|
|
1086
707
|
```typescript
|
|
1087
|
-
const
|
|
708
|
+
const access = await client.user.getUserAccess()
|
|
1088
709
|
|
|
1089
|
-
//
|
|
1090
|
-
{
|
|
1091
|
-
|
|
1092
|
-
|
|
710
|
+
// Off-ramp capabilities
|
|
711
|
+
if (access.capabilities.offramp.active) {
|
|
712
|
+
console.log('Off-ramp features:', access.capabilities.offramp.features)
|
|
713
|
+
// US: 'us_bank_account', 'us_debit_card'
|
|
714
|
+
// CA: 'ca_bank_account'
|
|
1093
715
|
}
|
|
1094
|
-
```
|
|
1095
|
-
|
|
1096
|
-
## Onramp Payments
|
|
1097
|
-
|
|
1098
|
-
Onramp Payments are orders to buy crypto stablecoins with a bank transfer. Upon creating an onramp payment, you will receive deposit instructions to fulfill that order. When the bank transfer has been received and disbursed, the status of that onramp payment will change.
|
|
1099
|
-
|
|
1100
|
-
### Create onramp payment
|
|
1101
716
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
// Example response
|
|
1112
|
-
|
|
1113
|
-
{
|
|
1114
|
-
"id": "653fab35ad263e5ae8b0e605",
|
|
1115
|
-
"amount": 100,
|
|
1116
|
-
"feeAmount": 1.5,
|
|
1117
|
-
"depositInstructions": {
|
|
1118
|
-
"amount": 101.5,
|
|
1119
|
-
"currency": "USD",
|
|
1120
|
-
"bankName": "Bank of Nowhere",
|
|
1121
|
-
"bankAddress": "1800 North Pole St., Orlando, FL 32801",
|
|
1122
|
-
"bankBeneficiaryName": "Bridge Ventures Inc",
|
|
1123
|
-
"bankRoutingNumber": "123456789",
|
|
1124
|
-
"bankAccountNumber": "11223344556677",
|
|
1125
|
-
"paymentMethod": "WIRE",
|
|
1126
|
-
"depositMessage": "BVI72D90851F051F4189",
|
|
1127
|
-
},
|
|
1128
|
-
"network": "ethereum",
|
|
1129
|
-
"token": "USDC",
|
|
1130
|
-
"address": "0xbb76483e33e01315438d8f6cf1aee9c9b85f433b",
|
|
1131
|
-
"status": "AWAITING_FUNDS",
|
|
1132
|
-
"createdAt": "2023-10-30T13:10:13.521Z",
|
|
717
|
+
// On-ramp capabilities
|
|
718
|
+
if (access.capabilities.onramp.active) {
|
|
719
|
+
console.log('On-ramp features:', access.capabilities.onramp.features)
|
|
720
|
+
// May include: 'ach_purchase', 'wire_purchase'
|
|
721
|
+
} else {
|
|
722
|
+
for (const req of access.capabilities.onramp.requirements) {
|
|
723
|
+
console.log(`${req.type}: ${req.description}`)
|
|
724
|
+
}
|
|
1133
725
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
726
|
```
|
|
1137
727
|
|
|
1138
|
-
###
|
|
728
|
+
### Activation Steps
|
|
729
|
+
|
|
730
|
+
#### 1. Complete Platform KYC
|
|
1139
731
|
|
|
1140
732
|
```typescript
|
|
1141
|
-
const
|
|
1142
|
-
await client.onrampPayment.list()[
|
|
1143
|
-
// Example response
|
|
733
|
+
const access = await client.user.getUserAccess()
|
|
1144
734
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
feeAmount: 1.5,
|
|
1149
|
-
depositInstructions: {
|
|
1150
|
-
amount: 101.5,
|
|
1151
|
-
currency: 'USD',
|
|
1152
|
-
bankName: 'Bank of Nowhere',
|
|
1153
|
-
bankAddress: '1800 North Pole St., Orlando, FL 32801',
|
|
1154
|
-
bankBeneficiaryName: 'Bridge Ventures Inc',
|
|
1155
|
-
bankRoutingNumber: '123456789',
|
|
1156
|
-
bankAccountNumber: '11223344556677',
|
|
1157
|
-
paymentMethod: 'WIRE',
|
|
1158
|
-
depositMessage: 'BVI72D90851F051F4189',
|
|
1159
|
-
},
|
|
1160
|
-
network: 'ethereum',
|
|
1161
|
-
token: 'USDC',
|
|
1162
|
-
address: '0xbb76483e33e01315438d8f6cf1aee9c9b85f433b',
|
|
1163
|
-
status: 'AWAITING_FUNDS',
|
|
1164
|
-
createdAt: '2023-10-30T13:10:13.521Z',
|
|
735
|
+
if (!access.kycStatus.verified) {
|
|
736
|
+
if (access.kycRequirement?.actionUrl) {
|
|
737
|
+
console.log('Complete KYC at:', access.kycRequirement.actionUrl)
|
|
1165
738
|
}
|
|
1166
|
-
|
|
739
|
+
if (access.kycRequirement?.status === 'failed' && access.kycRequirement.retryable) {
|
|
740
|
+
await client.user.retryFailedVerification()
|
|
741
|
+
}
|
|
742
|
+
}
|
|
1167
743
|
```
|
|
1168
744
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
Instead of making a request to get information, webhooks send information to your specified endpoint as soon as an event occurs. Spritz's integration offers a variety of webhook events that can be crucial for maintaining data integrity and responsiveness in applications. Let's dive into how you can best utilize these.
|
|
1172
|
-
|
|
1173
|
-
### Supported webhook events
|
|
1174
|
-
|
|
1175
|
-
Spritz currently supports the following webhook events:
|
|
1176
|
-
|
|
1177
|
-
#### Account Events
|
|
1178
|
-
|
|
1179
|
-
- `account.created`: Triggered when a new account is created.
|
|
1180
|
-
- `account.updated`: Triggered when details of an account are updated.
|
|
1181
|
-
- `account.deleted`: Triggered when an account is deleted.
|
|
1182
|
-
|
|
1183
|
-
#### Payment Events
|
|
1184
|
-
|
|
1185
|
-
- `payment.created`: Triggered when a new payment is initiated.
|
|
1186
|
-
- `payment.updated`: Triggered when details of a payment are updated.
|
|
1187
|
-
- `payment.completed`: Triggered when a payment is successfully completed.
|
|
1188
|
-
- `payment.refunded`: Triggered when a payment is refunded.
|
|
1189
|
-
|
|
1190
|
-
#### Verification Events
|
|
1191
|
-
|
|
1192
|
-
- `verification.status.updated`: Triggered when a user's verification status changes.
|
|
1193
|
-
|
|
1194
|
-
These events allow you to respond to changes in the account and payments for a user.
|
|
1195
|
-
|
|
1196
|
-
### Setting up webhooks
|
|
1197
|
-
|
|
1198
|
-
To set up a webhook with Spritz, you'll need to provide:
|
|
1199
|
-
|
|
1200
|
-
1. **URL**: The endpoint URL where Spritz will send the webhook data.
|
|
1201
|
-
2. **Events**: The specific events you want to listen for.
|
|
745
|
+
#### 2. Accept Terms of Service
|
|
1202
746
|
|
|
1203
747
|
```typescript
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
```
|
|
1209
|
-
|
|
1210
|
-
Upon receiving a webhook, your server will get a payload with the following structure:
|
|
748
|
+
const access = await client.user.getUserAccess()
|
|
749
|
+
const tosRequirement = access.capabilities.onramp.requirements.find(
|
|
750
|
+
(req) => req.type === 'terms_acceptance'
|
|
751
|
+
)
|
|
1211
752
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
753
|
+
if (tosRequirement?.actionUrl) {
|
|
754
|
+
// Display tosRequirement.actionUrl in a browser tab, iframe, or webview.
|
|
755
|
+
// Listen for the signedAgreementId via postMessage:
|
|
756
|
+
window.addEventListener('message', (event) => {
|
|
757
|
+
if (event.data.signedAgreementId) {
|
|
758
|
+
await client.onramp.acceptTermsOfService(event.data.signedAgreementId)
|
|
759
|
+
}
|
|
760
|
+
})
|
|
1217
761
|
}
|
|
1218
762
|
```
|
|
1219
763
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
#### List all webhooks
|
|
764
|
+
#### 3. Wait for Provider KYC
|
|
1223
765
|
|
|
1224
|
-
|
|
766
|
+
Provider KYC runs automatically after ToS acceptance. No action required — monitor the status:
|
|
1225
767
|
|
|
1226
768
|
```typescript
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
#### Delete a webhook
|
|
1232
|
-
|
|
1233
|
-
To delete a specific webhook by its ID:
|
|
769
|
+
const access = await client.user.getUserAccess()
|
|
770
|
+
const kycReq = access.capabilities.onramp.requirements.find(
|
|
771
|
+
(req) => req.type === 'identity_verification'
|
|
772
|
+
)
|
|
1234
773
|
|
|
1235
|
-
|
|
1236
|
-
const deletedWebhook = await client.webhook.delete('webhook-id-here')
|
|
1237
|
-
// Returns the deleted webhook object
|
|
774
|
+
// kycReq is undefined when complete, otherwise check kycReq.status ('pending' | 'failed')
|
|
1238
775
|
```
|
|
1239
776
|
|
|
1240
|
-
|
|
777
|
+
Use the `capabilities.updated` webhook event to be notified when the user's capabilities change.
|
|
1241
778
|
|
|
1242
|
-
|
|
779
|
+
### Virtual Accounts
|
|
1243
780
|
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
You can verify webhook authenticity by computing the HMAC signature and comparing it to the Signature header included in the webhook request.
|
|
1247
|
-
|
|
1248
|
-
#### Example: Verifying a webhook signature (Node.js)
|
|
781
|
+
Once on-ramp is active, users can create virtual accounts to receive fiat deposits:
|
|
1249
782
|
|
|
1250
783
|
```typescript
|
|
1251
|
-
import {
|
|
1252
|
-
|
|
1253
|
-
const signature = createHmac("sha256", <YOUR_WEBHOOK_SECRET>)
|
|
1254
|
-
.update(<REQUEST_BODY_AS_JSON_STRING>) // JSON.stringify(payload)
|
|
1255
|
-
.digest("hex");
|
|
1256
|
-
```
|
|
784
|
+
import { PaymentNetwork, onrampSupportedTokens } from '@spritz-finance/api-client'
|
|
1257
785
|
|
|
1258
|
-
|
|
786
|
+
// Check supported tokens for a network
|
|
787
|
+
const tokens = onrampSupportedTokens[PaymentNetwork.Ethereum]
|
|
788
|
+
// ['USDC', 'USDT', 'DAI', 'USDP', 'PYUSD']
|
|
1259
789
|
|
|
1260
|
-
|
|
790
|
+
// Create a virtual account
|
|
791
|
+
const virtualAccount = await client.virtualAccounts.create({
|
|
792
|
+
network: PaymentNetwork.Ethereum,
|
|
793
|
+
address: '0xYourEthereumAddress',
|
|
794
|
+
token: 'USDC',
|
|
795
|
+
})
|
|
1261
796
|
|
|
1262
|
-
|
|
797
|
+
// Deposit instructions for funding via ACH/wire
|
|
798
|
+
const { bankName, bankAccountNumber, bankRoutingNumber, bankAddress } =
|
|
799
|
+
virtualAccount.depositInstructions
|
|
1263
800
|
|
|
1264
|
-
|
|
1265
|
-
const
|
|
1266
|
-
// Returns: { success: true }
|
|
801
|
+
// List all virtual accounts
|
|
802
|
+
const accounts = await client.virtualAccounts.list()
|
|
1267
803
|
```
|
|
1268
804
|
|
|
1269
|
-
|
|
805
|
+
### Supported Tokens
|
|
1270
806
|
|
|
1271
|
-
|
|
807
|
+
| Network | Tokens |
|
|
808
|
+
| --------- | ---------------------------- |
|
|
809
|
+
| Ethereum | USDC, USDT, DAI, USDP, PYUSD |
|
|
810
|
+
| Polygon | USDC |
|
|
811
|
+
| Base | USDC |
|
|
812
|
+
| Arbitrum | USDC |
|
|
813
|
+
| Avalanche | USDC |
|
|
814
|
+
| Optimism | USDC |
|
|
815
|
+
| Solana | USDC, PYUSD |
|
|
816
|
+
| Tron | USDT |
|
|
1272
817
|
|
|
1273
|
-
|
|
818
|
+
## ACH Onramp (Direct Debit)
|
|
1274
819
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
Before a user can on-ramp, they must:
|
|
1278
|
-
|
|
1279
|
-
1. Complete platform-level KYC (identity verification)
|
|
1280
|
-
2. Accept the third-party on-ramp provider's Terms of Service
|
|
1281
|
-
3. Wait for the provider's KYC to process (happens automatically after accepting ToS)
|
|
820
|
+
ACH onramp lets users convert USD from their bank account into USDC delivered to a Solana wallet. The flow is:
|
|
1282
821
|
|
|
1283
|
-
|
|
822
|
+
1. **Link bank account** via Plaid → funding source created automatically
|
|
823
|
+
2. **Bind wallet** — user signs a message proving wallet ownership
|
|
824
|
+
3. **Create deposit** — user authorizes an ACH debit, USDC released to wallet
|
|
1284
825
|
|
|
1285
|
-
|
|
826
|
+
For a complete walkthrough with code examples, request/response schemas, and deposit lifecycle documentation, see the **[ACH Onramp Integration Guide](docs/ach-onramp-guide.md)**.
|
|
1286
827
|
|
|
1287
|
-
|
|
1288
|
-
const accessCapabilities = await client.user.getUserAccess()
|
|
828
|
+
A standalone sandbox demo is available at `scripts/sandbox/ach-onramp.html` — open it in a browser to walk through the full flow interactively.
|
|
1289
829
|
|
|
1290
|
-
|
|
1291
|
-
if (accessCapabilities.capabilities.onramp.active) {
|
|
1292
|
-
console.log('User can on-ramp!')
|
|
1293
|
-
console.log('Available features:', accessCapabilities.capabilities.onramp.features)
|
|
1294
|
-
// Features may include: 'ach_purchase', 'wire_purchase'
|
|
1295
|
-
} else {
|
|
1296
|
-
console.log('User needs to complete requirements:')
|
|
1297
|
-
accessCapabilities.capabilities.onramp.requirements.forEach((req) => {
|
|
1298
|
-
console.log(`- ${req.type}: ${req.description}`)
|
|
1299
|
-
if (req.actionUrl) {
|
|
1300
|
-
console.log(` Action URL: ${req.actionUrl}`)
|
|
1301
|
-
}
|
|
1302
|
-
})
|
|
1303
|
-
}
|
|
830
|
+
## Sandbox
|
|
1304
831
|
|
|
1305
|
-
|
|
1306
|
-
if (accessCapabilities.capabilities.offramp.active) {
|
|
1307
|
-
console.log('User can off-ramp!')
|
|
1308
|
-
console.log('Available features:', accessCapabilities.capabilities.offramp.features)
|
|
1309
|
-
// US users: 'us_bank_account', 'us_debit_card'
|
|
1310
|
-
// CA users: 'ca_bank_account'
|
|
1311
|
-
}
|
|
1312
|
-
```
|
|
832
|
+
Use `Environment.Sandbox` for development and testing. The sandbox environment is available at `https://sandbox.spritz.finance`.
|
|
1313
833
|
|
|
1314
|
-
###
|
|
834
|
+
### Bypassing KYC
|
|
1315
835
|
|
|
1316
|
-
|
|
836
|
+
In sandbox, you can skip identity verification to speed up testing:
|
|
1317
837
|
|
|
1318
838
|
```typescript
|
|
1319
|
-
|
|
839
|
+
// Simulate successful US KYC verification
|
|
840
|
+
await client.sandbox.bypassKyc()
|
|
1320
841
|
|
|
1321
|
-
//
|
|
1322
|
-
|
|
1323
|
-
// User needs to complete platform KYC
|
|
1324
|
-
if (access.kycRequirement) {
|
|
1325
|
-
if (access.kycRequirement.actionUrl) {
|
|
1326
|
-
// Direct user to complete KYC
|
|
1327
|
-
console.log('Complete KYC at:', access.kycRequirement.actionUrl)
|
|
1328
|
-
}
|
|
1329
|
-
if (access.kycRequirement.status === 'failed' && access.kycRequirement.retryable) {
|
|
1330
|
-
// User can retry failed verification
|
|
1331
|
-
await client.user.retryFailedVerification()
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
```
|
|
1336
|
-
|
|
1337
|
-
#### 2. Accept Terms of Service
|
|
842
|
+
// Simulate KYC for a specific country
|
|
843
|
+
await client.sandbox.bypassKyc({ country: 'CA' })
|
|
1338
844
|
|
|
1339
|
-
|
|
845
|
+
// Simulate a failed KYC check
|
|
846
|
+
await client.sandbox.bypassKyc({ failed: true })
|
|
847
|
+
```
|
|
1340
848
|
|
|
1341
|
-
|
|
1342
|
-
const access = await client.user.getUserAccess()
|
|
849
|
+
This endpoint returns 403 in production.
|
|
1343
850
|
|
|
1344
|
-
|
|
1345
|
-
const tosRequirement = access.capabilities.onramp.requirements.find(
|
|
1346
|
-
(req) => req.type === 'terms_acceptance'
|
|
1347
|
-
)
|
|
851
|
+
## Webhooks
|
|
1348
852
|
|
|
1349
|
-
|
|
1350
|
-
// Use this URL to guide the customer towards Terms of Service acceptance
|
|
1351
|
-
// You can embed this URL in an iFrame or display in a new browser window
|
|
853
|
+
### Events
|
|
1352
854
|
|
|
1353
|
-
|
|
1354
|
-
// Listen to the postMessage event for the signedAgreementId
|
|
1355
|
-
window.addEventListener('message', (event) => {
|
|
1356
|
-
if (event.data.signedAgreementId) {
|
|
1357
|
-
// Use the agreement ID to complete acceptance
|
|
1358
|
-
await client.onramp.acceptTermsOfService(event.data.signedAgreementId)
|
|
1359
|
-
}
|
|
1360
|
-
})
|
|
855
|
+
#### Account Events
|
|
1361
856
|
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
```
|
|
857
|
+
- `account.created` — new account created
|
|
858
|
+
- `account.updated` — account details updated
|
|
859
|
+
- `account.deleted` — account deleted
|
|
1366
860
|
|
|
1367
|
-
####
|
|
861
|
+
#### Payment Events
|
|
1368
862
|
|
|
1369
|
-
|
|
863
|
+
- `payment.created` — payment initiated
|
|
864
|
+
- `payment.updated` — payment details updated
|
|
865
|
+
- `payment.completed` — payment completed
|
|
866
|
+
- `payment.refunded` — payment refunded
|
|
1370
867
|
|
|
1371
|
-
|
|
1372
|
-
// After accepting ToS, provider KYC is processed automatically
|
|
1373
|
-
// You can monitor the status but no action is required
|
|
1374
|
-
const access = await client.user.getUserAccess()
|
|
868
|
+
#### Verification Events
|
|
1375
869
|
|
|
1376
|
-
|
|
1377
|
-
const kycRequirement = access.capabilities.onramp.requirements.find(
|
|
1378
|
-
(req) => req.type === 'identity_verification'
|
|
1379
|
-
)
|
|
870
|
+
- `verification.status.updated` — user verification status changed
|
|
1380
871
|
|
|
1381
|
-
|
|
1382
|
-
switch (kycRequirement.status) {
|
|
1383
|
-
case 'pending':
|
|
1384
|
-
console.log('Provider KYC is being processed automatically')
|
|
1385
|
-
break
|
|
1386
|
-
case 'failed':
|
|
1387
|
-
console.log('Provider KYC failed - user may need to retry')
|
|
1388
|
-
break
|
|
1389
|
-
default:
|
|
1390
|
-
// KYC completed successfully if no requirement exists
|
|
1391
|
-
console.log('Provider KYC complete!')
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
```
|
|
872
|
+
#### Capability Events
|
|
1395
873
|
|
|
1396
|
-
|
|
874
|
+
- `capabilities.updated` — user capabilities changed
|
|
1397
875
|
|
|
1398
|
-
|
|
876
|
+
### Setup
|
|
1399
877
|
|
|
1400
878
|
```typescript
|
|
1401
|
-
// Set up a webhook to monitor capability updates
|
|
1402
879
|
const webhook = await client.webhook.create({
|
|
1403
|
-
|
|
1404
|
-
|
|
880
|
+
url: 'https://my.webhook.url/spritz',
|
|
881
|
+
events: ['account.created', 'account.updated', 'payment.completed'],
|
|
1405
882
|
})
|
|
1406
|
-
|
|
1407
|
-
// In your webhook handler:
|
|
1408
|
-
// When you receive a 'capabilities.updated' event, check the user's access again
|
|
1409
|
-
const updatedAccess = await client.user.getUserAccess()
|
|
1410
|
-
if (updatedAccess.capabilities.onramp.active) {
|
|
1411
|
-
console.log('User can now on-ramp!')
|
|
1412
|
-
}
|
|
1413
883
|
```
|
|
1414
884
|
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
Once on-ramp is active, users can create virtual accounts to receive funds:
|
|
885
|
+
Webhook payloads have the following shape:
|
|
1418
886
|
|
|
1419
|
-
```
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
console.log('Supported tokens on Ethereum:', supportedTokens)
|
|
1425
|
-
// Output: ['USDC', 'USDT', 'DAI', 'USDP', 'PYUSD']
|
|
1426
|
-
|
|
1427
|
-
// Create a virtual account
|
|
1428
|
-
const virtualAccount = await client.virtualAccounts.create({
|
|
1429
|
-
network: PaymentNetwork.Ethereum,
|
|
1430
|
-
address: '0xYourEthereumAddress',
|
|
1431
|
-
token: 'USDC',
|
|
1432
|
-
})
|
|
1433
|
-
|
|
1434
|
-
// The virtual account includes deposit instructions
|
|
1435
|
-
if (virtualAccount.depositInstructions) {
|
|
1436
|
-
const instructions = virtualAccount.depositInstructions
|
|
1437
|
-
console.log('Bank Name:', instructions.bankName)
|
|
1438
|
-
console.log('Account Number:', instructions.bankAccountNumber)
|
|
1439
|
-
console.log('Routing Number:', instructions.bankRoutingNumber)
|
|
1440
|
-
console.log('Bank Address:', instructions.bankAddress)
|
|
1441
|
-
// User sends wire/ACH to these details to fund their account
|
|
887
|
+
```json
|
|
888
|
+
{
|
|
889
|
+
"userId": "user-id",
|
|
890
|
+
"id": "resource-id",
|
|
891
|
+
"eventName": "event-name"
|
|
1442
892
|
}
|
|
1443
893
|
```
|
|
1444
894
|
|
|
1445
|
-
###
|
|
895
|
+
### Management
|
|
1446
896
|
|
|
1447
897
|
```typescript
|
|
1448
|
-
//
|
|
1449
|
-
const
|
|
1450
|
-
|
|
1451
|
-
accounts.forEach((account) => {
|
|
1452
|
-
console.log(`Network: ${account.network}`)
|
|
1453
|
-
console.log(`Address: ${account.address}`)
|
|
1454
|
-
console.log(`Token: ${account.token}`)
|
|
1455
|
-
console.log(`Deposited: ${account.deposited}`)
|
|
898
|
+
// List all webhooks
|
|
899
|
+
const webhooks = await client.webhook.list()
|
|
1456
900
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
}
|
|
1460
|
-
})
|
|
901
|
+
// Delete a webhook
|
|
902
|
+
await client.webhook.delete('webhook-id')
|
|
1461
903
|
```
|
|
1462
904
|
|
|
1463
|
-
###
|
|
1464
|
-
|
|
1465
|
-
The following tokens are supported on each network for virtual accounts:
|
|
905
|
+
### Security and Signing
|
|
1466
906
|
|
|
1467
|
-
|
|
1468
|
-
- **Polygon**: USDC
|
|
1469
|
-
- **Base**: USDC
|
|
1470
|
-
- **Arbitrum**: USDC
|
|
1471
|
-
- **Avalanche**: USDC
|
|
1472
|
-
- **Optimism**: USDC
|
|
1473
|
-
- **Solana**: USDC, PYUSD
|
|
1474
|
-
- **Tron**: USDT
|
|
907
|
+
Webhook requests are signed with HMAC SHA256 using your webhook secret. The signature is sent in the `Signature` HTTP header.
|
|
1475
908
|
|
|
1476
|
-
|
|
909
|
+
#### Setting a Webhook Secret
|
|
1477
910
|
|
|
1478
911
|
```typescript
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
const client = SpritzApiClient.initialize({
|
|
1482
|
-
environment: Environment.Production,
|
|
1483
|
-
integrationKey: 'YOUR_INTEGRATION_KEY',
|
|
1484
|
-
})
|
|
1485
|
-
|
|
1486
|
-
// Set user API key
|
|
1487
|
-
client.setApiKey(userApiKey)
|
|
1488
|
-
|
|
1489
|
-
async function setupOnramp() {
|
|
1490
|
-
// 1. Check current access
|
|
1491
|
-
const access = await client.user.getUserAccess()
|
|
1492
|
-
|
|
1493
|
-
if (!access.capabilities.onramp.active) {
|
|
1494
|
-
console.log('On-ramp not active. Requirements:')
|
|
1495
|
-
|
|
1496
|
-
for (const req of access.capabilities.onramp.requirements) {
|
|
1497
|
-
if (req.type === 'terms_acceptance' && req.actionUrl) {
|
|
1498
|
-
// Direct user to accept terms
|
|
1499
|
-
console.log('Accept terms at:', req.actionUrl)
|
|
1500
|
-
// After acceptance, call:
|
|
1501
|
-
// await client.onramp.acceptTermsOfService(agreementId)
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
912
|
+
await client.webhook.updateWebhookSecret('your-secret')
|
|
913
|
+
```
|
|
1504
914
|
|
|
1505
|
-
|
|
1506
|
-
}
|
|
915
|
+
#### Verifying Signatures
|
|
1507
916
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
network: PaymentNetwork.Ethereum,
|
|
1511
|
-
address: '0xUserWalletAddress',
|
|
1512
|
-
token: 'USDC',
|
|
1513
|
-
})
|
|
917
|
+
```typescript
|
|
918
|
+
import { createHmac } from 'crypto'
|
|
1514
919
|
|
|
1515
|
-
|
|
1516
|
-
console.log('Send funds to:', virtualAccount.depositInstructions)
|
|
920
|
+
const expected = createHmac('sha256', WEBHOOK_SECRET).update(JSON.stringify(payload)).digest('hex')
|
|
1517
921
|
|
|
1518
|
-
|
|
922
|
+
if (expected !== request.headers['signature']) {
|
|
923
|
+
throw new Error('Invalid webhook signature')
|
|
1519
924
|
}
|
|
1520
|
-
|
|
1521
|
-
setupOnramp().then((success) => {
|
|
1522
|
-
if (success) {
|
|
1523
|
-
console.log('On-ramp setup complete!')
|
|
1524
|
-
}
|
|
1525
|
-
})
|
|
1526
925
|
```
|