@spritz-finance/api-client 0.4.26 → 0.4.28
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 +486 -1115
- package/dist/spritz-api-client.cjs +78 -70
- package/dist/spritz-api-client.d.ts +11 -9
- package/dist/spritz-api-client.mjs +80 -72
- package/package.json +17 -11
package/README.md
CHANGED
|
@@ -1,178 +1,125 @@
|
|
|
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
|
-
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
// Create a user
|
|
43
|
-
const user = await client.user.create({
|
|
44
|
-
email: 'user@example.com',
|
|
28
|
+
environment: Environment.Sandbox,
|
|
29
|
+
integrationKey: 'YOUR_INTEGRATION_KEY_HERE',
|
|
45
30
|
})
|
|
46
31
|
|
|
47
|
-
//
|
|
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
|
-
- [Get payment limits for an account](#get-payment-limits-for-an-account)
|
|
116
|
-
- [Onramp Payments](#onramp-payments)
|
|
117
|
-
- [Create an onramp payment](#create-onramp-payment)
|
|
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)
|
|
119
91
|
- [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)
|
|
130
|
-
|
|
131
|
-
## API Overview
|
|
92
|
+
- [Events](#events)
|
|
93
|
+
- [Setup](#setup)
|
|
94
|
+
- [Management](#management)
|
|
95
|
+
- [Security and Signing](#security-and-signing)
|
|
132
96
|
|
|
133
|
-
|
|
97
|
+
## Authentication
|
|
134
98
|
|
|
135
|
-
|
|
99
|
+
Spritz uses two levels of authentication:
|
|
136
100
|
|
|
137
|
-
|
|
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.
|
|
101
|
+
- **Integration key** — identifies your application. Provided by Spritz.
|
|
102
|
+
- **User API key** — scoped to a single user. Returned when you create a user.
|
|
160
103
|
|
|
161
104
|
```typescript
|
|
162
105
|
import { SpritzApiClient, Environment } from '@spritz-finance/api-client'
|
|
163
106
|
|
|
164
107
|
const client = SpritzApiClient.initialize({
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
108
|
+
environment: Environment.Sandbox,
|
|
109
|
+
integrationKey: 'YOUR_INTEGRATION_KEY_HERE',
|
|
110
|
+
apiKey: 'YOUR_USER_API_KEY_HERE', // omit if no user exists yet
|
|
168
111
|
})
|
|
169
112
|
```
|
|
170
113
|
|
|
171
|
-
|
|
114
|
+
After creating a user, set their API key on the client:
|
|
172
115
|
|
|
173
|
-
|
|
116
|
+
```typescript
|
|
117
|
+
client.setApiKey(user.apiKey)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Users
|
|
174
121
|
|
|
175
|
-
|
|
122
|
+
### Creating a User
|
|
176
123
|
|
|
177
124
|
```typescript
|
|
178
125
|
const user = await client.user.create({
|
|
@@ -180,504 +127,339 @@ const user = await client.user.create({
|
|
|
180
127
|
})
|
|
181
128
|
|
|
182
129
|
// Response
|
|
183
|
-
|
|
184
|
-
email:
|
|
185
|
-
userId:
|
|
186
|
-
apiKey:
|
|
130
|
+
{
|
|
131
|
+
email: 'bilbo@shiremail.net',
|
|
132
|
+
userId: '62d17d3b377dab6c1342136e',
|
|
133
|
+
apiKey: 'ak_ZTBGDcjfdTg3NmYtZDJlZC00ZjYyLThlMDMtZmYwNDJiZDRlMWZm',
|
|
187
134
|
}
|
|
188
135
|
```
|
|
189
136
|
|
|
190
|
-
|
|
137
|
+
Creating a user with an email that already exists will throw an error.
|
|
191
138
|
|
|
192
|
-
|
|
139
|
+
### Reauthorization
|
|
193
140
|
|
|
194
|
-
|
|
195
|
-
client.setApiKey(user.apiKey)
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Now you're ready to issue requests on behalf of the user.
|
|
199
|
-
|
|
200
|
-
### Reauthorizing a user
|
|
201
|
-
|
|
202
|
-
There is a scenrio where you may need to get access to a users API key again. This can happen if you are trying to sign in a user that already has a Spritz account, or if you have lost access to their API key. In this case, you can reauthorize the user by providing their email. The process is that we will send the user an OTP code to their email, and then the user must pass that code on to you to confirm that they are allowing you to interact with their account on their behalf.
|
|
141
|
+
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
142
|
|
|
204
143
|
```typescript
|
|
144
|
+
// Request an OTP code sent to the user's email
|
|
205
145
|
const { success } = await client.user.requestApiKey('bilbo@shiremail.net')
|
|
206
146
|
|
|
147
|
+
// Confirm with the OTP code the user provides
|
|
207
148
|
const { apiKey, userId, email } = await client.user.authorizeApiKeyWithOTP({
|
|
208
|
-
|
|
209
|
-
|
|
149
|
+
email: 'bilbo@shiremail.net',
|
|
150
|
+
otp: '123456',
|
|
210
151
|
})
|
|
211
152
|
```
|
|
212
153
|
|
|
213
|
-
###
|
|
214
|
-
|
|
215
|
-
Use this to fetch the user's basic data
|
|
154
|
+
### User Data
|
|
216
155
|
|
|
217
156
|
```typescript
|
|
218
157
|
const userData = await client.user.getCurrentUser()
|
|
219
158
|
```
|
|
220
159
|
|
|
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.
|
|
228
|
-
|
|
229
|
-
### Process
|
|
160
|
+
### Identity Verification
|
|
230
161
|
|
|
231
|
-
|
|
162
|
+
All users must complete identity verification before using the platform. New users start with a verification status of `NotStarted`.
|
|
232
163
|
|
|
233
|
-
|
|
164
|
+
The user's verification data is included in the `getCurrentUser` response, including verification status, verification URL, verified country, and retry capability.
|
|
234
165
|
|
|
235
|
-
|
|
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:
|
|
166
|
+
#### Getting Verification Parameters
|
|
242
167
|
|
|
243
168
|
```typescript
|
|
244
|
-
// Get verification parameters from Spritz
|
|
245
169
|
const verificationParams = await client.user.getVerificationParams()
|
|
246
170
|
|
|
247
|
-
//
|
|
171
|
+
// Returns:
|
|
248
172
|
// - inquiryId: Unique identifier for this verification inquiry
|
|
249
173
|
// - verificationUrl: URL for hosted verification
|
|
250
|
-
// - sessionToken: Token for use with
|
|
174
|
+
// - sessionToken: Token for use with Persona's Embedded Flow
|
|
251
175
|
// - verificationUrlExpiresAt: Expiration timestamp for the verification URL
|
|
252
176
|
```
|
|
253
177
|
|
|
254
|
-
####
|
|
178
|
+
#### Option 1: Verification URL
|
|
255
179
|
|
|
256
|
-
|
|
180
|
+
The simplest integration — redirect the user to the hosted verification flow:
|
|
257
181
|
|
|
258
182
|
```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.
|
|
276
|
-
|
|
277
|
-
#### Method 2: Embedded Flow (Advanced Integration)
|
|
183
|
+
const { verificationUrl, verificationUrlExpiresAt } = await client.user.getVerificationParams()
|
|
278
184
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
|
185
|
+
// Open in a browser tab, iframe, or mobile web view.
|
|
186
|
+
// The URL is single-use and short-lived. If it expires or the user
|
|
187
|
+
// doesn't complete verification, call getVerificationParams() again.
|
|
293
188
|
```
|
|
294
189
|
|
|
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.
|
|
303
|
-
|
|
304
|
-
### Verification Metadata (Failed Verifications)
|
|
190
|
+
#### Option 2: Embedded Flow
|
|
305
191
|
|
|
306
|
-
|
|
192
|
+
For full control over the UX, use the `inquiryId` and `sessionToken` with [Persona's Embedded Flow](https://docs.withpersona.com/quickstart-embedded-flow):
|
|
307
193
|
|
|
308
|
-
|
|
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:
|
|
194
|
+
```typescript
|
|
195
|
+
const { inquiryId, sessionToken } = await client.user.getVerificationParams()
|
|
318
196
|
|
|
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
|
|
197
|
+
// Use inquiryId (and sessionToken if present) with Persona's SDK
|
|
198
|
+
// to embed the verification flow directly in your app.
|
|
199
|
+
```
|
|
327
200
|
|
|
328
|
-
####
|
|
201
|
+
#### Handling Verification Failures
|
|
329
202
|
|
|
330
|
-
When
|
|
203
|
+
When verification fails, the `verificationMetadata` field on the user object provides the failure reason:
|
|
331
204
|
|
|
332
|
-
|
|
333
|
-
|
|
205
|
+
| Failure Reason | Description |
|
|
206
|
+
| -------------------------- | ---------------------------- |
|
|
207
|
+
| `verify_sms` | SMS verification failed |
|
|
208
|
+
| `documentary_verification` | Document verification failed |
|
|
209
|
+
| `risk_check` | Risk assessment failed |
|
|
210
|
+
| `kyc_check` | KYC check failed |
|
|
211
|
+
| `watchlist_screening` | Watchlist screening failed |
|
|
212
|
+
| `selfie_check` | Selfie verification failed |
|
|
213
|
+
| `address_invalid` | Invalid address |
|
|
214
|
+
| `duplicate_identity` | Identity already exists |
|
|
334
215
|
|
|
335
|
-
|
|
216
|
+
For `duplicate_identity` failures, `matchedEmail` indicates whether the duplicate was created through your integration:
|
|
336
217
|
|
|
337
218
|
```typescript
|
|
338
219
|
const userData = await client.user.getCurrentUser()
|
|
339
220
|
|
|
340
221
|
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
|
-
}
|
|
222
|
+
const matchedEmail = userData.verificationMetadata.details.matchedEmail
|
|
223
|
+
|
|
224
|
+
if (matchedEmail) {
|
|
225
|
+
// Duplicate exists within your integration — guide user to their existing account
|
|
226
|
+
console.log(`Already verified as: ${matchedEmail}`)
|
|
227
|
+
} else {
|
|
228
|
+
// Duplicate exists in a different integration (e.g., the main Spritz app)
|
|
229
|
+
console.log('Identity already verified with another Spritz account')
|
|
230
|
+
}
|
|
352
231
|
}
|
|
353
232
|
```
|
|
354
233
|
|
|
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
234
|
## Accounts
|
|
412
235
|
|
|
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.
|
|
236
|
+
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
237
|
|
|
433
238
|
### Bank Accounts
|
|
434
239
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
#### List user bank accounts
|
|
438
|
-
|
|
439
|
-
To retrieve all bank accounts linked to a user:
|
|
240
|
+
#### List
|
|
440
241
|
|
|
441
242
|
```typescript
|
|
442
243
|
const bankAccounts = await client.bankAccount.list()
|
|
443
244
|
```
|
|
444
245
|
|
|
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
246
|
```typescript
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
247
|
+
// Example response
|
|
248
|
+
;[
|
|
249
|
+
{
|
|
250
|
+
id: '62d17d3b377dab6c1342136e',
|
|
251
|
+
name: 'Precious Savings',
|
|
252
|
+
type: 'BankAccount',
|
|
253
|
+
bankAccountType: 'USBankAccount',
|
|
254
|
+
bankAccountSubType: 'Checking',
|
|
255
|
+
userId: '62d17d3b377dab6c1342136e',
|
|
256
|
+
accountNumber: '1234567',
|
|
257
|
+
bankAccountDetails: {
|
|
258
|
+
routingNumber: '00000123',
|
|
259
|
+
},
|
|
260
|
+
country: 'US',
|
|
261
|
+
currency: 'USD',
|
|
262
|
+
email: 'bilbo@shiremail.net',
|
|
263
|
+
institution: {
|
|
264
|
+
id: '62d27d4b277dab3c1342126e',
|
|
265
|
+
name: 'Shire Bank',
|
|
266
|
+
logo: 'https://tinyurl.com/shire-bank-logo',
|
|
267
|
+
},
|
|
268
|
+
ownedByUser: true,
|
|
269
|
+
createdAt: '2023-05-03T11:25:02.401Z',
|
|
270
|
+
deliveryMethods: ['STANDARD', 'INSTANT'],
|
|
271
|
+
},
|
|
272
|
+
]
|
|
471
273
|
```
|
|
472
274
|
|
|
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
|
-
```
|
|
275
|
+
#### Create US Bank Account
|
|
490
276
|
|
|
491
277
|
```typescript
|
|
492
278
|
import { BankAccountType, BankAccountSubType } from '@spritz-finance/api-client'
|
|
493
279
|
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
280
|
+
const bankAccount = await client.bankAccount.create(BankAccountType.USBankAccount, {
|
|
281
|
+
accountNumber: '123456789',
|
|
282
|
+
routingNumber: '987654321',
|
|
283
|
+
name: 'Precious Savings',
|
|
284
|
+
ownedByUser: true,
|
|
285
|
+
subType: BankAccountSubType.Savings,
|
|
500
286
|
})
|
|
501
287
|
```
|
|
502
288
|
|
|
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:
|
|
289
|
+
Input fields:
|
|
508
290
|
|
|
509
291
|
```typescript
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
institutionNumber: string
|
|
518
|
-
subType: BankAccountSubType
|
|
292
|
+
interface USBankAccountInput {
|
|
293
|
+
accountNumber: string
|
|
294
|
+
routingNumber: string
|
|
295
|
+
subType: BankAccountSubType
|
|
296
|
+
name?: string | null
|
|
297
|
+
email?: string | null
|
|
298
|
+
ownedByUser?: boolean | null
|
|
519
299
|
}
|
|
520
300
|
```
|
|
521
301
|
|
|
302
|
+
#### Create Canadian Bank Account
|
|
303
|
+
|
|
522
304
|
```typescript
|
|
523
305
|
import { BankAccountType, BankAccountSubType } from '@spritz-finance/api-client'
|
|
524
306
|
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
307
|
+
const bankAccount = await client.bankAccount.create(BankAccountType.CABankAccount, {
|
|
308
|
+
accountNumber: '123456789',
|
|
309
|
+
transitNumber: '12345',
|
|
310
|
+
institutionNumber: '123',
|
|
311
|
+
name: 'Precious Savings',
|
|
312
|
+
ownedByUser: true,
|
|
313
|
+
subType: BankAccountSubType.Savings,
|
|
532
314
|
})
|
|
533
315
|
```
|
|
534
316
|
|
|
535
|
-
|
|
317
|
+
Input fields:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
interface CABankAccountInput {
|
|
321
|
+
accountNumber: string
|
|
322
|
+
transitNumber: string
|
|
323
|
+
institutionNumber: string
|
|
324
|
+
name: string
|
|
325
|
+
subType: BankAccountSubType
|
|
326
|
+
email?: string
|
|
327
|
+
ownedByUser?: boolean | null
|
|
328
|
+
}
|
|
329
|
+
```
|
|
536
330
|
|
|
537
|
-
|
|
331
|
+
### Debit Cards
|
|
538
332
|
|
|
539
|
-
|
|
333
|
+
Supported networks: **Visa** and **Mastercard**.
|
|
540
334
|
|
|
541
|
-
|
|
335
|
+
#### List
|
|
542
336
|
|
|
543
337
|
```typescript
|
|
544
338
|
const debitCards = await client.debitCard.list()
|
|
545
339
|
```
|
|
546
340
|
|
|
547
|
-
The `debitCard.list()` method returns an array of user-linked debit cards:
|
|
548
|
-
|
|
549
341
|
```typescript
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
342
|
+
// Example response
|
|
343
|
+
;[
|
|
344
|
+
{
|
|
345
|
+
id: '62d17d3b377dab6c1342136e',
|
|
346
|
+
type: 'DebitCard',
|
|
347
|
+
name: 'My Visa Debit',
|
|
348
|
+
userId: '62d17d3b377dab6c1342136e',
|
|
349
|
+
country: 'US',
|
|
350
|
+
currency: 'USD',
|
|
351
|
+
payable: true,
|
|
352
|
+
debitCardNetwork: 'Visa',
|
|
353
|
+
expirationDate: '12/25',
|
|
354
|
+
cardNumber: '4111111111111111',
|
|
355
|
+
mask: '1111',
|
|
356
|
+
createdAt: '2023-01-01T00:00:00Z',
|
|
357
|
+
paymentCount: 5,
|
|
358
|
+
externalId: 'ext-123',
|
|
359
|
+
},
|
|
567
360
|
]
|
|
568
361
|
```
|
|
569
362
|
|
|
570
|
-
####
|
|
571
|
-
|
|
572
|
-
To add a new debit card for a user:
|
|
363
|
+
#### Create
|
|
573
364
|
|
|
574
365
|
```typescript
|
|
575
|
-
import { DebitCardNetwork } from '@spritz-finance/api-client'
|
|
576
|
-
|
|
577
366
|
const debitCard = await client.debitCard.create({
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
367
|
+
cardNumber: '4111111111111111', // 13-19 digits
|
|
368
|
+
expirationDate: '12/25', // MM/YY
|
|
369
|
+
name: 'My Visa Debit', // optional
|
|
581
370
|
})
|
|
582
371
|
```
|
|
583
372
|
|
|
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
373
|
### Bills
|
|
600
374
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
#### List user bills
|
|
604
|
-
|
|
605
|
-
To retrieve all bill accounts associated with a user:
|
|
375
|
+
#### List
|
|
606
376
|
|
|
607
377
|
```typescript
|
|
608
378
|
const bills = await client.bill.list()
|
|
609
379
|
```
|
|
610
380
|
|
|
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
381
|
```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
|
-
|
|
382
|
+
// Example response
|
|
383
|
+
;[
|
|
384
|
+
{
|
|
385
|
+
id: '62d17d3b377dab6c1342136e',
|
|
386
|
+
name: 'Precious Credit Card',
|
|
387
|
+
type: 'Bill',
|
|
388
|
+
billType: 'CreditCard',
|
|
389
|
+
userId: '62d17d3b377dab6c1342136e',
|
|
390
|
+
mask: '4567',
|
|
391
|
+
originator: 'User',
|
|
392
|
+
payable: true,
|
|
393
|
+
verifying: false,
|
|
394
|
+
billAccountDetails: {
|
|
395
|
+
balance: 240.23,
|
|
396
|
+
amountDue: 28.34,
|
|
397
|
+
openedAt: '2023-05-03T11:25:02.401Z',
|
|
398
|
+
lastPaymentAmount: null,
|
|
399
|
+
lastPaymentDate: null,
|
|
400
|
+
nextPaymentDueDate: '2023-06-03T11:25:02.401Z',
|
|
401
|
+
nextPaymentMinimumAmount: 28.34,
|
|
402
|
+
lastStatementBalance: 180.23,
|
|
403
|
+
remainingStatementBalance: null,
|
|
404
|
+
},
|
|
405
|
+
country: 'US',
|
|
406
|
+
currency: 'USD',
|
|
407
|
+
dataSync: {
|
|
408
|
+
lastSync: '2023-05-03T11:25:02.401Z',
|
|
409
|
+
syncStatus: 'Active',
|
|
410
|
+
},
|
|
411
|
+
institution: {
|
|
412
|
+
id: '62d27d4b277dab3c1342126e',
|
|
413
|
+
name: 'Shire Bank Credit Card',
|
|
414
|
+
logo: 'https://tinyurl.com/shire-bank-logo',
|
|
415
|
+
},
|
|
416
|
+
createdAt: '2023-05-03T11:25:02.401Z',
|
|
417
|
+
deliveryMethods: ['STANDARD'],
|
|
646
418
|
},
|
|
647
|
-
createdAt: '2023-05-03T11:25:02.401Z',
|
|
648
|
-
deliveryMethods: ['STANDARD'],
|
|
649
|
-
},
|
|
650
419
|
]
|
|
651
420
|
```
|
|
652
421
|
|
|
653
|
-
####
|
|
422
|
+
#### Create
|
|
654
423
|
|
|
655
|
-
|
|
424
|
+
Adding a bill requires the institution ID and the account number:
|
|
656
425
|
|
|
657
426
|
```typescript
|
|
658
427
|
import { BillType } from '@spritz-finance/api-client'
|
|
659
428
|
|
|
660
429
|
const institutions = await client.institution.popularUSBillInstitutions(BillType.CreditCard)
|
|
661
|
-
const
|
|
662
|
-
|
|
430
|
+
const bill = await client.bill.create(institutions[0].id, '12345678913213', BillType.CreditCard)
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
#### Finding Bill Institutions
|
|
663
434
|
|
|
664
|
-
|
|
435
|
+
```typescript
|
|
436
|
+
// Popular institutions (optionally filtered by bill type)
|
|
437
|
+
const popular = await client.institution.popularUSBillInstitutions()
|
|
438
|
+
const mortgages = await client.institution.popularUSBillInstitutions(BillType.Mortgage)
|
|
439
|
+
|
|
440
|
+
// Search by name
|
|
441
|
+
const results = await client.institution.searchUSBillInstitutions('american express')
|
|
442
|
+
const filtered = await client.institution.searchUSBillInstitutions(
|
|
443
|
+
'american express',
|
|
444
|
+
BillType.CreditCard
|
|
445
|
+
)
|
|
665
446
|
```
|
|
666
447
|
|
|
667
|
-
### Virtual
|
|
448
|
+
### Virtual Cards
|
|
668
449
|
|
|
669
|
-
|
|
450
|
+
Virtual cards are crypto-funded payment cards.
|
|
670
451
|
|
|
671
|
-
#### Fetch
|
|
452
|
+
#### Fetch
|
|
672
453
|
|
|
673
|
-
|
|
454
|
+
Returns card details excluding sensitive fields (card number, CVV):
|
|
674
455
|
|
|
675
456
|
```typescript
|
|
676
457
|
const virtualCard = await client.virtualCard.fetch()
|
|
677
458
|
```
|
|
678
459
|
|
|
679
460
|
```typescript
|
|
680
|
-
|
|
461
|
+
// Example response
|
|
462
|
+
{
|
|
681
463
|
id: '62d17d3b377dab6c1342136e',
|
|
682
464
|
type: 'VirtualCard',
|
|
683
465
|
virtualCardType: 'USVirtualDebitCard',
|
|
@@ -703,7 +485,7 @@ const virtualCard = {
|
|
|
703
485
|
}
|
|
704
486
|
```
|
|
705
487
|
|
|
706
|
-
#### Create
|
|
488
|
+
#### Create
|
|
707
489
|
|
|
708
490
|
```typescript
|
|
709
491
|
import { VirtualCardType } from '@spritz-finance/api-client'
|
|
@@ -711,189 +493,71 @@ import { VirtualCardType } from '@spritz-finance/api-client'
|
|
|
711
493
|
const virtualCard = await client.virtualCard.create(VirtualCardType.USVirtualDebitCard)
|
|
712
494
|
```
|
|
713
495
|
|
|
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.
|
|
717
|
-
|
|
718
|
-
We currently support and maintain the following packages for the card rendering process:
|
|
719
|
-
|
|
720
|
-
- [React Library](https://www.npmjs.com/package/@spritz-finance/react-secure-elements)
|
|
721
|
-
- [React Native Library](https://www.npmjs.com/package/@spritz-finance/react-native-secure-elements)
|
|
722
|
-
|
|
723
|
-
## Address Book
|
|
724
|
-
|
|
725
|
-
Each account created in Spritz is allocated a unique on-chain payment address for each network. Tokens transferred to this address will be picked up and credited to the account. A list of the addresses, one per network, is available under the `paymentAddresses` property. Refer to the Spritz UI for which tokens are accepted for these addresses -- generally, we accept at least USDC and USDT on all networks which we integrate with.
|
|
726
|
-
|
|
727
|
-
```typescript
|
|
728
|
-
[
|
|
729
|
-
{
|
|
730
|
-
id: '62d17d3b377dab6c1342136e',
|
|
731
|
-
name: 'Precious Credit Card',
|
|
732
|
-
type: 'Bill',
|
|
733
|
-
billType: 'CreditCard',
|
|
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
|
|
496
|
+
#### Displaying Sensitive Card Details
|
|
773
497
|
|
|
774
|
-
|
|
498
|
+
To render the full card number and CVV, use the `renderSecret` from the fetch response with one of the Spritz secure element libraries:
|
|
775
499
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
### Deleting accounts
|
|
500
|
+
- [React](https://www.npmjs.com/package/@spritz-finance/react-secure-elements)
|
|
501
|
+
- [React Native](https://www.npmjs.com/package/@spritz-finance/react-native-secure-elements)
|
|
781
502
|
|
|
782
|
-
|
|
503
|
+
### Address Book
|
|
783
504
|
|
|
784
|
-
|
|
505
|
+
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.
|
|
785
506
|
|
|
786
507
|
```typescript
|
|
787
|
-
|
|
508
|
+
// Included in account responses
|
|
509
|
+
{
|
|
510
|
+
paymentAddresses: [
|
|
511
|
+
{ network: 'ethereum', address: '0xc0ffee254729296a45a3885639AC7E10F9d54979' },
|
|
512
|
+
{ network: 'polygon', address: '0xc0ffee254729296a45a3885639AC7E10F9d54979' },
|
|
513
|
+
],
|
|
514
|
+
}
|
|
788
515
|
```
|
|
789
516
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
To remove a debit card from a user's account:
|
|
517
|
+
### Renaming Accounts
|
|
793
518
|
|
|
794
519
|
```typescript
|
|
795
|
-
await client.
|
|
520
|
+
await client.bankAccount.rename('account-id', 'New Name')
|
|
521
|
+
await client.debitCard.rename('card-id', 'New Name')
|
|
522
|
+
await client.bill.rename('bill-id', 'New Name')
|
|
796
523
|
```
|
|
797
524
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
To remove a bill from a user's account, you can use the following endpoint. You only need to specify the ID of the bill that you want to delete as an argument.
|
|
525
|
+
### Deleting Accounts
|
|
801
526
|
|
|
802
527
|
```typescript
|
|
803
|
-
await client.
|
|
528
|
+
await client.bankAccount.delete('account-id')
|
|
529
|
+
await client.debitCard.delete('card-id')
|
|
530
|
+
await client.bill.delete('bill-id')
|
|
804
531
|
```
|
|
805
532
|
|
|
806
|
-
##
|
|
533
|
+
## Payments (Off-ramp)
|
|
807
534
|
|
|
808
|
-
|
|
535
|
+
### Payment Flow
|
|
809
536
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
const popularInstitutions = await client.institution.popularUSBillInstitutions()
|
|
537
|
+
1. **Select an account** — choose the bank account, debit card, or bill to pay.
|
|
538
|
+
2. **Create a payment request** — specify amount, account ID, and blockchain network.
|
|
539
|
+
3. **Get transaction data** — call `getWeb3PaymentParams` (EVM) or `getSolanaPaymentParams` (Solana).
|
|
540
|
+
4. **Execute the blockchain transaction** — sign and submit from the user's wallet.
|
|
541
|
+
5. **Check payment status** — query the resulting fiat payment.
|
|
816
542
|
|
|
817
|
-
|
|
818
|
-
const popularInstitutions = await client.institution.popularUSBillInstitutions(BillType.Mortgage)
|
|
819
|
-
```
|
|
543
|
+
> 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).
|
|
820
544
|
|
|
821
|
-
###
|
|
545
|
+
### Creating a Payment Request
|
|
822
546
|
|
|
823
547
|
```typescript
|
|
824
|
-
|
|
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.
|
|
548
|
+
import { PaymentNetwork, AmountMode } from '@spritz-finance/api-client'
|
|
849
549
|
|
|
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
550
|
const paymentRequest = await client.paymentRequest.create({
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
551
|
+
amount: 100,
|
|
552
|
+
accountId: account.id,
|
|
553
|
+
network: PaymentNetwork.Ethereum,
|
|
554
|
+
deliveryMethod: 'INSTANT', // optional
|
|
555
|
+
amountMode: AmountMode.TOTAL_AMOUNT, // optional, defaults to AMOUNT_RECEIVED
|
|
878
556
|
})
|
|
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
557
|
```
|
|
883
558
|
|
|
884
559
|
```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
560
|
// Example response
|
|
896
|
-
|
|
897
561
|
{
|
|
898
562
|
id: '645399c8c1ac408007b12273',
|
|
899
563
|
userId: '63d12d3B577fab6c6382136e',
|
|
@@ -903,108 +567,98 @@ const paymentRequest = await client.paymentRequest.create({
|
|
|
903
567
|
feeAmount: 0,
|
|
904
568
|
amountDue: 100,
|
|
905
569
|
network: 'ethereum',
|
|
906
|
-
createdAt: '2023-05-04T11:40:56.488Z'
|
|
570
|
+
createdAt: '2023-05-04T11:40:56.488Z',
|
|
907
571
|
}
|
|
908
572
|
```
|
|
909
573
|
|
|
910
|
-
|
|
574
|
+
#### Amount Mode
|
|
911
575
|
|
|
912
|
-
|
|
576
|
+
- **`AMOUNT_RECEIVED`** (default) — the recipient receives the specified amount; fees are added on top.
|
|
577
|
+
- **`TOTAL_AMOUNT`** — the specified amount includes fees; the recipient receives less.
|
|
913
578
|
|
|
914
|
-
|
|
579
|
+
#### Fee Subsidies
|
|
915
580
|
|
|
916
|
-
|
|
917
|
-
import {PaymentNetwork} from '@spritz-finance/api-client';
|
|
581
|
+
Integrators can subsidize transaction fees on behalf of users. This is a gated feature — contact Spritz to enable it.
|
|
918
582
|
|
|
583
|
+
```typescript
|
|
919
584
|
const paymentRequest = await client.paymentRequest.create({
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
585
|
+
amount: 100,
|
|
586
|
+
accountId: account.id,
|
|
587
|
+
network: PaymentNetwork.Ethereum,
|
|
588
|
+
feeSubsidyPercentage: '100', // percentage of fee to cover
|
|
589
|
+
maxFeeSubsidyAmount: '5', // cap per transaction in USD
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
// Fee = $3 → integrator pays $3, user pays $0
|
|
593
|
+
// Fee = $8 → integrator pays $5, user pays $3
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
Subsidized amounts are invoiced to the integrator separately.
|
|
924
597
|
|
|
598
|
+
### Fulfilling a Payment — EVM
|
|
599
|
+
|
|
600
|
+
For EVM networks, you interact with the SpritzPay smart contract ([deployment addresses](https://docs.spritz.finance/docs/deployment-addresses)):
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
925
603
|
const transactionData = await client.paymentRequest.getWeb3PaymentParams({
|
|
926
|
-
|
|
927
|
-
|
|
604
|
+
paymentRequest,
|
|
605
|
+
paymentTokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC
|
|
928
606
|
})
|
|
929
607
|
|
|
930
608
|
// Example response
|
|
931
|
-
|
|
932
609
|
{
|
|
933
610
|
contractAddress: '0xbF7Abc15f00a8C2d6b13A952c58d12b7c194A8D0',
|
|
934
611
|
method: 'payWithToken',
|
|
935
|
-
calldata: '
|
|
612
|
+
calldata: '0xd71d9632...',
|
|
936
613
|
value: null,
|
|
937
614
|
requiredTokenInput: '100000000',
|
|
938
615
|
}
|
|
939
616
|
```
|
|
940
617
|
|
|
941
|
-
|
|
618
|
+
Use `contractAddress` as `to`, `calldata` as `data`, and `value` to build the transaction. Check `requiredTokenInput` against the user's balance before submitting.
|
|
942
619
|
|
|
943
|
-
###
|
|
944
|
-
|
|
945
|
-
For Solana payments, you need to obtain a versioned transaction that can be signed and submitted to the Solana network.
|
|
620
|
+
### Fulfilling a Payment — Solana
|
|
946
621
|
|
|
947
622
|
```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
623
|
const transactionData = await client.paymentRequest.getSolanaPaymentParams({
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
624
|
+
paymentRequest,
|
|
625
|
+
paymentTokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
|
|
626
|
+
signer: 'YourSolanaWalletAddress',
|
|
960
627
|
})
|
|
961
628
|
|
|
962
629
|
// Example response
|
|
963
|
-
|
|
964
630
|
{
|
|
965
|
-
versionedTransaction: VersionedTransaction, //
|
|
966
|
-
transactionSerialized: '
|
|
631
|
+
versionedTransaction: VersionedTransaction, // ready to sign
|
|
632
|
+
transactionSerialized: 'base64...', // base64-encoded alternative
|
|
967
633
|
}
|
|
968
634
|
```
|
|
969
635
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
### Transaction fees
|
|
636
|
+
### Transaction Fees
|
|
973
637
|
|
|
974
|
-
|
|
638
|
+
Fees apply once monthly volume exceeds $100. To check the fee for a given amount:
|
|
975
639
|
|
|
976
640
|
```typescript
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
// Example response
|
|
980
|
-
0.01
|
|
641
|
+
const fee = await client.paymentRequest.transactionPrice(101)
|
|
642
|
+
// Returns: 0.01
|
|
981
643
|
```
|
|
982
644
|
|
|
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
|
|
645
|
+
### Retrieving Payments
|
|
988
646
|
|
|
989
|
-
Payments
|
|
647
|
+
Payments are created once a payment request reaches `Confirmed` status.
|
|
990
648
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
- **value**: The amount transferred (in the token's smallest unit)
|
|
995
|
-
- **network**: The blockchain network used (e.g., 'ethereum', 'polygon', etc.)
|
|
996
|
-
|
|
997
|
-
This allows you to track the on-chain transaction that corresponds to each fiat payment.
|
|
649
|
+
```typescript
|
|
650
|
+
// By payment ID
|
|
651
|
+
const payment = await client.payment.fetchById('6368e3a3ec516e9572bbd23b')
|
|
998
652
|
|
|
999
|
-
|
|
653
|
+
// By payment request ID
|
|
654
|
+
const payment = await client.payment.getForPaymentRequest(paymentRequest.id)
|
|
1000
655
|
|
|
1001
|
-
|
|
656
|
+
// All payments for an account
|
|
657
|
+
const payments = await client.payment.listForAccount(account.id)
|
|
658
|
+
```
|
|
1002
659
|
|
|
1003
660
|
```typescript
|
|
1004
|
-
const payment = await client.payment.fetchById('6368e3a3ec516e9572bbd23b');
|
|
1005
|
-
|
|
1006
661
|
// Example response
|
|
1007
|
-
|
|
1008
662
|
{
|
|
1009
663
|
id: '6368e3a3ec516e9572bbd23b',
|
|
1010
664
|
userId: '63d12d3B577fab6c6382136e',
|
|
@@ -1014,505 +668,222 @@ const payment = await client.payment.fetchById('6368e3a3ec516e9572bbd23b');
|
|
|
1014
668
|
feeAmount: null,
|
|
1015
669
|
createdAt: '2022-11-07T10:53:23.998Z',
|
|
1016
670
|
transaction: {
|
|
1017
|
-
hash: '
|
|
671
|
+
hash: '0x1234...abcdef',
|
|
1018
672
|
from: '0xYourWalletAddress',
|
|
1019
673
|
asset: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
|
1020
674
|
value: 100000000,
|
|
1021
|
-
network: 'ethereum'
|
|
1022
|
-
}
|
|
675
|
+
network: 'ethereum',
|
|
676
|
+
},
|
|
1023
677
|
}
|
|
1024
678
|
```
|
|
1025
679
|
|
|
1026
|
-
###
|
|
1027
|
-
|
|
1028
|
-
```typescript
|
|
1029
|
-
import {PaymentNetwork} from '@spritz-finance/api-client';
|
|
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);
|
|
1038
|
-
|
|
1039
|
-
// Example response
|
|
1040
|
-
|
|
1041
|
-
{
|
|
1042
|
-
id: '6368e3a3ec516e9572bbd23b',
|
|
1043
|
-
userId: '63d12d3B577fab6c6382136e',
|
|
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
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
```
|
|
1053
|
-
|
|
1054
|
-
### Retrieve all payments for an account
|
|
1055
|
-
|
|
1056
|
-
```typescript
|
|
1057
|
-
const payments = await client.payment.listForAccount(account.id)
|
|
1058
|
-
|
|
1059
|
-
// Example response
|
|
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
|
-
```
|
|
1081
|
-
|
|
1082
|
-
### Get payment limits for an account
|
|
1083
|
-
|
|
1084
|
-
Retrieve the payment limits for a specific account, including the per-transaction limit and the remaining daily volume.
|
|
680
|
+
### Payment Limits
|
|
1085
681
|
|
|
1086
682
|
```typescript
|
|
1087
683
|
const limits = await client.payment.getPaymentLimits(account.id)
|
|
1088
684
|
|
|
1089
685
|
// Example response
|
|
1090
686
|
{
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
}
|
|
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
|
-
|
|
1102
|
-
```typescript
|
|
1103
|
-
const onrampPayment = await client.onrampPayment.create({
|
|
1104
|
-
token: 'USDC' // Supported: currently only 'USDC'
|
|
1105
|
-
network: 'ethereum' // supported: 'ethereum', 'polygon', 'avalanche'
|
|
1106
|
-
amount: 100, // How much token to purchase (100 USDC)
|
|
1107
|
-
address: '0xbB76483e33e01315438D8F6CF1Aee9C9b85f433b', // Wallet address to disburse tokens to
|
|
1108
|
-
paymentMethod: 'ACH' // 'WIRE' or 'ACH'
|
|
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",
|
|
687
|
+
perTransaction: 20000,
|
|
688
|
+
dailyRemainingVolume: 150000,
|
|
1133
689
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
690
|
```
|
|
1137
691
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
```typescript
|
|
1141
|
-
const payments =
|
|
1142
|
-
await client.onrampPayment.list()[
|
|
1143
|
-
// Example response
|
|
1144
|
-
|
|
1145
|
-
{
|
|
1146
|
-
id: '653fab35ad263e5ae8b0e605',
|
|
1147
|
-
amount: 100,
|
|
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',
|
|
1165
|
-
}
|
|
1166
|
-
]
|
|
1167
|
-
```
|
|
1168
|
-
|
|
1169
|
-
## Webhooks
|
|
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:
|
|
692
|
+
## On-ramp
|
|
1199
693
|
|
|
1200
|
-
|
|
1201
|
-
2. **Events**: The specific events you want to listen for.
|
|
1202
|
-
|
|
1203
|
-
```typescript
|
|
1204
|
-
const webhook = await client.webhook.create({
|
|
1205
|
-
url: 'https://my.webhook.url/spritz',
|
|
1206
|
-
events: ['account.created', 'account.updated', 'account.deleted'],
|
|
1207
|
-
})
|
|
1208
|
-
```
|
|
1209
|
-
|
|
1210
|
-
Upon receiving a webhook, your server will get a payload with the following structure:
|
|
1211
|
-
|
|
1212
|
-
```json
|
|
1213
|
-
{
|
|
1214
|
-
"userId": "user-id-here",
|
|
1215
|
-
"id": "resource-id-here",
|
|
1216
|
-
"eventName": "name-of-the-event-here"
|
|
1217
|
-
}
|
|
1218
|
-
```
|
|
1219
|
-
|
|
1220
|
-
### Managing webhooks
|
|
1221
|
-
|
|
1222
|
-
#### List all webhooks
|
|
1223
|
-
|
|
1224
|
-
To retrieve all webhooks configured for your integration:
|
|
1225
|
-
|
|
1226
|
-
```typescript
|
|
1227
|
-
const webhooks = await client.webhook.list()
|
|
1228
|
-
// Returns an array of webhook configurations
|
|
1229
|
-
```
|
|
1230
|
-
|
|
1231
|
-
#### Delete a webhook
|
|
1232
|
-
|
|
1233
|
-
To delete a specific webhook by its ID:
|
|
1234
|
-
|
|
1235
|
-
```typescript
|
|
1236
|
-
const deletedWebhook = await client.webhook.delete('webhook-id-here')
|
|
1237
|
-
// Returns the deleted webhook object
|
|
1238
|
-
```
|
|
1239
|
-
|
|
1240
|
-
### Webhook security and signing
|
|
1241
|
-
|
|
1242
|
-
Each webhook request is signed using an HMAC SHA256 signature, based on the exact JSON payload sent in the body. This signature is included in the Signature HTTP header of the request.
|
|
1243
|
-
|
|
1244
|
-
The secret key used to compute the signature is the webhook secret you set when configuring your webhook integration. If you have not set a webhook secret, there will be no Signature header in the webhook request.
|
|
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)
|
|
1249
|
-
|
|
1250
|
-
```typescript
|
|
1251
|
-
import { createHmac } from "crypto";
|
|
1252
|
-
|
|
1253
|
-
const signature = createHmac("sha256", <YOUR_WEBHOOK_SECRET>)
|
|
1254
|
-
.update(<REQUEST_BODY_AS_JSON_STRING>) // JSON.stringify(payload)
|
|
1255
|
-
.digest("hex");
|
|
1256
|
-
```
|
|
1257
|
-
|
|
1258
|
-
Ensure that the computed signature matches the Signature header received in the webhook request before processing the payload.
|
|
1259
|
-
|
|
1260
|
-
### Setting webhook secret
|
|
1261
|
-
|
|
1262
|
-
To add or update a webhook secret for signing webhook requests:
|
|
1263
|
-
|
|
1264
|
-
```typescript
|
|
1265
|
-
const result = await client.webhook.updateWebhookSecret('your-webhook-secret-here')
|
|
1266
|
-
// Returns: { success: true }
|
|
1267
|
-
```
|
|
1268
|
-
|
|
1269
|
-
This secret will be used to sign all subsequent webhook requests sent to your endpoint. Always store your webhook secret securely and never expose it in client-side code.
|
|
1270
|
-
|
|
1271
|
-
## On-ramp Setup Guide
|
|
1272
|
-
|
|
1273
|
-
The on-ramp feature allows users to purchase cryptocurrency using traditional payment methods (ACH, Wire transfers). Before users can on-ramp, they must complete identity verification and accept terms of service.
|
|
694
|
+
The on-ramp feature allows users to purchase crypto stablecoins via ACH or wire transfer.
|
|
1274
695
|
|
|
1275
696
|
### Prerequisites
|
|
1276
697
|
|
|
1277
|
-
Before a user can on-ramp, they must:
|
|
1278
|
-
|
|
1279
698
|
1. Complete platform-level KYC (identity verification)
|
|
1280
699
|
2. Accept the third-party on-ramp provider's Terms of Service
|
|
1281
|
-
3.
|
|
1282
|
-
|
|
1283
|
-
### Checking User Access Capabilities
|
|
700
|
+
3. Provider KYC processes automatically after ToS acceptance
|
|
1284
701
|
|
|
1285
|
-
|
|
702
|
+
### Checking User Access
|
|
1286
703
|
|
|
1287
704
|
```typescript
|
|
1288
|
-
const
|
|
705
|
+
const access = await client.user.getUserAccess()
|
|
706
|
+
|
|
707
|
+
// Off-ramp capabilities
|
|
708
|
+
if (access.capabilities.offramp.active) {
|
|
709
|
+
console.log('Off-ramp features:', access.capabilities.offramp.features)
|
|
710
|
+
// US: 'us_bank_account', 'us_debit_card'
|
|
711
|
+
// CA: 'ca_bank_account'
|
|
712
|
+
}
|
|
1289
713
|
|
|
1290
|
-
//
|
|
1291
|
-
if (
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
// Features may include: 'ach_purchase', 'wire_purchase'
|
|
714
|
+
// On-ramp capabilities
|
|
715
|
+
if (access.capabilities.onramp.active) {
|
|
716
|
+
console.log('On-ramp features:', access.capabilities.onramp.features)
|
|
717
|
+
// May include: 'ach_purchase', 'wire_purchase'
|
|
1295
718
|
} else {
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
console.log(`- ${req.type}: ${req.description}`)
|
|
1299
|
-
if (req.actionUrl) {
|
|
1300
|
-
console.log(` Action URL: ${req.actionUrl}`)
|
|
719
|
+
for (const req of access.capabilities.onramp.requirements) {
|
|
720
|
+
console.log(`${req.type}: ${req.description}`)
|
|
1301
721
|
}
|
|
1302
|
-
})
|
|
1303
722
|
}
|
|
1304
723
|
```
|
|
1305
724
|
|
|
1306
|
-
###
|
|
725
|
+
### Activation Steps
|
|
1307
726
|
|
|
1308
|
-
#### 1.
|
|
727
|
+
#### 1. Complete Platform KYC
|
|
1309
728
|
|
|
1310
729
|
```typescript
|
|
1311
730
|
const access = await client.user.getUserAccess()
|
|
1312
731
|
|
|
1313
|
-
// Check platform-level KYC first
|
|
1314
732
|
if (!access.kycStatus.verified) {
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
if (access.kycRequirement.actionUrl) {
|
|
1318
|
-
// Direct user to complete KYC
|
|
1319
|
-
console.log('Complete KYC at:', access.kycRequirement.actionUrl)
|
|
733
|
+
if (access.kycRequirement?.actionUrl) {
|
|
734
|
+
console.log('Complete KYC at:', access.kycRequirement.actionUrl)
|
|
1320
735
|
}
|
|
1321
|
-
if (access.kycRequirement
|
|
1322
|
-
|
|
1323
|
-
await client.user.retryFailedVerification()
|
|
736
|
+
if (access.kycRequirement?.status === 'failed' && access.kycRequirement.retryable) {
|
|
737
|
+
await client.user.retryFailedVerification()
|
|
1324
738
|
}
|
|
1325
|
-
}
|
|
1326
739
|
}
|
|
1327
740
|
```
|
|
1328
741
|
|
|
1329
742
|
#### 2. Accept Terms of Service
|
|
1330
743
|
|
|
1331
|
-
Once platform KYC is complete, the user needs to accept the third-party on-ramp provider's Terms of Service:
|
|
1332
|
-
|
|
1333
744
|
```typescript
|
|
1334
745
|
const access = await client.user.getUserAccess()
|
|
1335
|
-
|
|
1336
|
-
// Find the Terms of Service requirement
|
|
1337
746
|
const tosRequirement = access.capabilities.onramp.requirements.find(
|
|
1338
|
-
|
|
747
|
+
(req) => req.type === 'terms_acceptance'
|
|
1339
748
|
)
|
|
1340
749
|
|
|
1341
|
-
if (tosRequirement
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
// Use the agreement ID to complete acceptance
|
|
1350
|
-
await client.onramp.acceptTermsOfService(event.data.signedAgreementId)
|
|
1351
|
-
}
|
|
1352
|
-
})
|
|
1353
|
-
|
|
1354
|
-
// Direct user to review the terms at tosRequirement.actionUrl
|
|
1355
|
-
console.log('Terms of Service URL:', tosRequirement.actionUrl)
|
|
750
|
+
if (tosRequirement?.actionUrl) {
|
|
751
|
+
// Display tosRequirement.actionUrl in a browser tab, iframe, or webview.
|
|
752
|
+
// Listen for the signedAgreementId via postMessage:
|
|
753
|
+
window.addEventListener('message', (event) => {
|
|
754
|
+
if (event.data.signedAgreementId) {
|
|
755
|
+
await client.onramp.acceptTermsOfService(event.data.signedAgreementId)
|
|
756
|
+
}
|
|
757
|
+
})
|
|
1356
758
|
}
|
|
1357
759
|
```
|
|
1358
760
|
|
|
1359
|
-
#### 3. Provider KYC
|
|
761
|
+
#### 3. Wait for Provider KYC
|
|
1360
762
|
|
|
1361
|
-
|
|
763
|
+
Provider KYC runs automatically after ToS acceptance. No action required — monitor the status:
|
|
1362
764
|
|
|
1363
765
|
```typescript
|
|
1364
|
-
// After accepting ToS, provider KYC is processed automatically
|
|
1365
|
-
// You can monitor the status but no action is required
|
|
1366
766
|
const access = await client.user.getUserAccess()
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
const kycRequirement = access.capabilities.onramp.requirements.find(
|
|
1370
|
-
(req) => req.type === 'identity_verification'
|
|
767
|
+
const kycReq = access.capabilities.onramp.requirements.find(
|
|
768
|
+
(req) => req.type === 'identity_verification'
|
|
1371
769
|
)
|
|
1372
770
|
|
|
1373
|
-
|
|
1374
|
-
switch (kycRequirement.status) {
|
|
1375
|
-
case 'pending':
|
|
1376
|
-
console.log('Provider KYC is being processed automatically')
|
|
1377
|
-
break
|
|
1378
|
-
case 'failed':
|
|
1379
|
-
console.log('Provider KYC failed - user may need to retry')
|
|
1380
|
-
break
|
|
1381
|
-
default:
|
|
1382
|
-
// KYC completed successfully if no requirement exists
|
|
1383
|
-
console.log('Provider KYC complete!')
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
771
|
+
// kycReq is undefined when complete, otherwise check kycReq.status ('pending' | 'failed')
|
|
1386
772
|
```
|
|
1387
773
|
|
|
1388
|
-
|
|
774
|
+
Use the `capabilities.updated` webhook event to be notified when the user's capabilities change.
|
|
1389
775
|
|
|
1390
|
-
|
|
776
|
+
### Virtual Accounts
|
|
1391
777
|
|
|
1392
|
-
|
|
1393
|
-
// Set up a webhook to monitor capability updates
|
|
1394
|
-
const webhook = await client.webhook.create({
|
|
1395
|
-
url: 'https://your-server.com/webhook',
|
|
1396
|
-
events: ['capabilities.updated'],
|
|
1397
|
-
})
|
|
1398
|
-
|
|
1399
|
-
// In your webhook handler:
|
|
1400
|
-
// When you receive a 'capabilities.updated' event, check the user's access again
|
|
1401
|
-
const updatedAccess = await client.user.getUserAccess()
|
|
1402
|
-
if (updatedAccess.capabilities.onramp.active) {
|
|
1403
|
-
console.log('User can now on-ramp!')
|
|
1404
|
-
}
|
|
1405
|
-
```
|
|
1406
|
-
|
|
1407
|
-
### Creating Virtual Accounts
|
|
1408
|
-
|
|
1409
|
-
Once on-ramp is active, users can create virtual accounts to receive funds:
|
|
778
|
+
Once on-ramp is active, users can create virtual accounts to receive fiat deposits:
|
|
1410
779
|
|
|
1411
780
|
```typescript
|
|
1412
781
|
import { PaymentNetwork, onrampSupportedTokens } from '@spritz-finance/api-client'
|
|
1413
782
|
|
|
1414
783
|
// Check supported tokens for a network
|
|
1415
|
-
const
|
|
1416
|
-
|
|
1417
|
-
// Output: ['USDC', 'USDT', 'DAI', 'USDP', 'PYUSD']
|
|
784
|
+
const tokens = onrampSupportedTokens[PaymentNetwork.Ethereum]
|
|
785
|
+
// ['USDC', 'USDT', 'DAI', 'USDP', 'PYUSD']
|
|
1418
786
|
|
|
1419
787
|
// Create a virtual account
|
|
1420
788
|
const virtualAccount = await client.virtualAccounts.create({
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
789
|
+
network: PaymentNetwork.Ethereum,
|
|
790
|
+
address: '0xYourEthereumAddress',
|
|
791
|
+
token: 'USDC',
|
|
1424
792
|
})
|
|
1425
793
|
|
|
1426
|
-
//
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
console.log('Bank Address:', instructions.bankAddress)
|
|
1433
|
-
// User sends wire/ACH to these details to fund their account
|
|
1434
|
-
}
|
|
794
|
+
// Deposit instructions for funding via ACH/wire
|
|
795
|
+
const { bankName, bankAccountNumber, bankRoutingNumber, bankAddress } =
|
|
796
|
+
virtualAccount.depositInstructions
|
|
797
|
+
|
|
798
|
+
// List all virtual accounts
|
|
799
|
+
const accounts = await client.virtualAccounts.list()
|
|
1435
800
|
```
|
|
1436
801
|
|
|
1437
|
-
###
|
|
802
|
+
### Supported Tokens
|
|
1438
803
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
804
|
+
| Network | Tokens |
|
|
805
|
+
| --------- | ---------------------------- |
|
|
806
|
+
| Ethereum | USDC, USDT, DAI, USDP, PYUSD |
|
|
807
|
+
| Polygon | USDC |
|
|
808
|
+
| Base | USDC |
|
|
809
|
+
| Arbitrum | USDC |
|
|
810
|
+
| Avalanche | USDC |
|
|
811
|
+
| Optimism | USDC |
|
|
812
|
+
| Solana | USDC, PYUSD |
|
|
813
|
+
| Tron | USDT |
|
|
814
|
+
|
|
815
|
+
## Webhooks
|
|
816
|
+
|
|
817
|
+
### Events
|
|
818
|
+
|
|
819
|
+
#### Account Events
|
|
820
|
+
|
|
821
|
+
- `account.created` — new account created
|
|
822
|
+
- `account.updated` — account details updated
|
|
823
|
+
- `account.deleted` — account deleted
|
|
824
|
+
|
|
825
|
+
#### Payment Events
|
|
1442
826
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
console.log(`Deposited: ${account.deposited}`)
|
|
827
|
+
- `payment.created` — payment initiated
|
|
828
|
+
- `payment.updated` — payment details updated
|
|
829
|
+
- `payment.completed` — payment completed
|
|
830
|
+
- `payment.refunded` — payment refunded
|
|
1448
831
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
832
|
+
#### Verification Events
|
|
833
|
+
|
|
834
|
+
- `verification.status.updated` — user verification status changed
|
|
835
|
+
|
|
836
|
+
#### Capability Events
|
|
837
|
+
|
|
838
|
+
- `capabilities.updated` — user capabilities changed
|
|
839
|
+
|
|
840
|
+
### Setup
|
|
841
|
+
|
|
842
|
+
```typescript
|
|
843
|
+
const webhook = await client.webhook.create({
|
|
844
|
+
url: 'https://my.webhook.url/spritz',
|
|
845
|
+
events: ['account.created', 'account.updated', 'payment.completed'],
|
|
1452
846
|
})
|
|
1453
847
|
```
|
|
1454
848
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
The following tokens are supported on each network for virtual accounts:
|
|
849
|
+
Webhook payloads have the following shape:
|
|
1458
850
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
-
|
|
1462
|
-
-
|
|
1463
|
-
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
- **Tron**: USDT
|
|
851
|
+
```json
|
|
852
|
+
{
|
|
853
|
+
"userId": "user-id",
|
|
854
|
+
"id": "resource-id",
|
|
855
|
+
"eventName": "event-name"
|
|
856
|
+
}
|
|
857
|
+
```
|
|
1467
858
|
|
|
1468
|
-
###
|
|
859
|
+
### Management
|
|
1469
860
|
|
|
1470
861
|
```typescript
|
|
1471
|
-
|
|
862
|
+
// List all webhooks
|
|
863
|
+
const webhooks = await client.webhook.list()
|
|
1472
864
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
})
|
|
865
|
+
// Delete a webhook
|
|
866
|
+
await client.webhook.delete('webhook-id')
|
|
867
|
+
```
|
|
1477
868
|
|
|
1478
|
-
|
|
1479
|
-
client.setApiKey(userApiKey)
|
|
869
|
+
### Security and Signing
|
|
1480
870
|
|
|
1481
|
-
|
|
1482
|
-
// 1. Check current access
|
|
1483
|
-
const access = await client.user.getUserAccess()
|
|
871
|
+
Webhook requests are signed with HMAC SHA256 using your webhook secret. The signature is sent in the `Signature` HTTP header.
|
|
1484
872
|
|
|
1485
|
-
|
|
1486
|
-
console.log('On-ramp not active. Requirements:')
|
|
873
|
+
#### Setting a Webhook Secret
|
|
1487
874
|
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
console.log('Accept terms at:', req.actionUrl)
|
|
1492
|
-
// After acceptance, call:
|
|
1493
|
-
// await client.onramp.acceptTermsOfService(agreementId)
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
875
|
+
```typescript
|
|
876
|
+
await client.webhook.updateWebhookSecret('your-secret')
|
|
877
|
+
```
|
|
1496
878
|
|
|
1497
|
-
|
|
1498
|
-
}
|
|
879
|
+
#### Verifying Signatures
|
|
1499
880
|
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
network: PaymentNetwork.Ethereum,
|
|
1503
|
-
address: '0xUserWalletAddress',
|
|
1504
|
-
token: 'USDC',
|
|
1505
|
-
})
|
|
881
|
+
```typescript
|
|
882
|
+
import { createHmac } from 'crypto'
|
|
1506
883
|
|
|
1507
|
-
|
|
1508
|
-
console.log('Send funds to:', virtualAccount.depositInstructions)
|
|
884
|
+
const expected = createHmac('sha256', WEBHOOK_SECRET).update(JSON.stringify(payload)).digest('hex')
|
|
1509
885
|
|
|
1510
|
-
|
|
886
|
+
if (expected !== request.headers['signature']) {
|
|
887
|
+
throw new Error('Invalid webhook signature')
|
|
1511
888
|
}
|
|
1512
|
-
|
|
1513
|
-
setupOnramp().then((success) => {
|
|
1514
|
-
if (success) {
|
|
1515
|
-
console.log('On-ramp setup complete!')
|
|
1516
|
-
}
|
|
1517
|
-
})
|
|
1518
889
|
```
|