@inflowpay/sdk
Version:
Minimal JavaScript/TypeScript SDK providing a pre-built CardElement widget for InflowPay payment processing
732 lines (576 loc) ⢠21.3 kB
Markdown
# InflowPay Payment SDK
Minimal JavaScript/TypeScript SDK providing a pre-built CardElement widget for secure card data collection and payment completion.
## Overview
The InflowPay SDK provides a simple, focused solution for collecting card data and completing pre-created payments. The SDK assumes payments are created on your backend with all billing information, and focuses solely on secure card collection and tokenization.
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Environments](#environments)
- [Quick Start](#quick-start)
- [Examples](#examples)
- [Key Concepts](#key-concepts)
- [Payment Flow](#payment-flow)
- [Payment Status](#payment-status)
- [3D Secure Flow](#3d-secure-3ds-flow)
- [API Reference](#api-reference)
- [PaymentSDK](#paymentsdk)
- [CardElement](#cardelement)
- [Testing](#testing)
- [Browser Support](#browser-support)
- [Security](#security)
- [Support](#support)
- [License](#license)
---
## Features
- š **Secure**: PCI-compliant card tokenization via tokenization proxy
- šÆ **Focused**: Collects only card data - billing info handled separately on your backend
- šØ **CardElement Widget**: Pre-built, customizable card input UI component
- š **3D Secure**: Built-in 3DS authentication support
- š¦ **TypeScript**: Full type definitions included
- š **Minimal**: Simple integration in 3 steps
- š± **Responsive**: Mobile-optimized card input interface
## Installation
```bash
npm install @inflowpay/sdk
```
### What's Exported
```typescript
// Main SDK class
import { PaymentSDK } from '@inflowpay/sdk';
// UI Component
import { CardElement } from '@inflowpay/sdk';
// Payment Status enum
import { PaymentStatus } from '@inflowpay/sdk';
// TypeScript types
import type {
SDKConfig,
CardData,
PaymentResult,
PaymentError,
CardElementOptions,
CardElementState,
CSSProperties
} from '@inflowpay/sdk';
// Version
import { VERSION } from '@inflowpay/sdk';
```
## Environments and Configuration
The SDK supports flexible configuration for different development and deployment scenarios:
### Configuration Options
#### 1. Production Environment (Live Payments)
```javascript
const sdk = new PaymentSDK({
apiKey: 'inflow_pub_prod_xxx',
environment: 'production',
});
```
**Default URLs:**
- API: `https://api.inflowpay.xyz`
- Tokenization: `https://api.basistheory.com/proxy` <!-- TODO: Replace with custom proxy URL once available -->
Use this for live payments with real money.
#### 2. Local Development with Mock Server
```javascript
const sdk = new PaymentSDK({
apiKey: 'inflow_pub_local_xxx',
environment: 'sandbox',
});
```
**Default URLs:**
- API: `http://localhost:3001`
- Tokenization: `http://localhost:3001/proxy/tokenize`
**Important:** The `sandbox` environment requires running the local mock server:
```bash
# Start mock server (in your SDK repo)
npm run mock-server
```
#### 3. Custom API URL (For InflowPay Developers)
For internal developers or when using a deployed dev/staging server:
```javascript
const sdk = new PaymentSDK({
apiKey: 'inflow_pub_dev_xxx',
environment: 'sandbox',
apiUrl: 'https://dev-api.inflowpay.xyz', // Custom API URL
tokenizationProxyUrl: 'https://dev-api.inflowpay.xyz/proxy/tokenize', // Custom tokenization URL
});
```
This allows you to:
- Test against deployed development servers
- Use staging environments
- Connect to any InflowPay API instance
### Environment Summary
| Environment | API URL | Tokenization URL | Use Case |
|-------------|---------|------------------|----------|
| **production** | `https://api.inflowpay.xyz` | `https://api.basistheory.com/proxy` | Live payments with real money |
| **sandbox** (default) | `http://localhost:3001` | `http://localhost:3001/proxy/tokenize` | Local development with mock server |
| **custom** | Your custom URL | Your custom proxy URL | Dev/staging servers, internal testing |
## Quick Start
The SDK provides a simple 3-step integration for collecting card data:
```javascript
import { PaymentSDK } from '@inflowpay/sdk';
// Step 1: Create payment on your backend with billing info
const payment = await fetch('/api/server/payment', {
method: 'POST',
body: JSON.stringify({
products: [{ name: 'Premium Plan', price: 4999 }],
currency: 'EUR',
customerEmail: 'customer@example.com',
billingCountry: 'FR',
firstName: 'Jean',
lastName: 'Dupont'
})
});
// Step 2: Initialize SDK and create CardElement
const sdk = new PaymentSDK({
apiKey: 'inflow_pub_xxx', // Public key (browser-safe)
environment: 'sandbox',
});
const cardElement = sdk.createCardElement({
container: '#card-element-container',
paymentId: payment.paymentId, // Pass payment ID from backend
onComplete: (result) => {
if (result.status === 'CHECKOUT_SUCCESS') {
window.location.href = '/success';
}
},
onError: (error) => alert(error.message)
});
// Step 3: Mount and let user enter card
cardElement.mount();
// User clicks button ā CardElement handles everything!
```
**That's it!** The CardElement handles card validation, tokenization, and payment completion automatically.
## Examples
### CardElement Integration
See [examples/card-element-integration/](./examples/card-element-integration/) for a complete working example:
```bash
# Clone the repository
git clone https://github.com/inflowpay/sdk.git
cd sdk
# Install dependencies and build
npm install
npm run build
# Start mock server
npm run mock-server
# Open the example in your browser
open examples/card-element-integration/index.html
```
**Features demonstrated:**
- Drop-in card payment widget
- Real-time card validation with visual feedback
- Automatic card tokenization and payment completion
- Customizable styling
- Error handling and 3DS redirects
### Mock Server for Development
The SDK includes a mock API server for local testing:
```bash
npm run mock-server
# Server runs at http://localhost:3001
```
**Test scenarios by email:**
- `decline@test.com` - Card declined (retryable)
- `insufficient@test.com` - Insufficient funds (retryable)
- `3ds@test.com` - Requires 3DS challenge
- `error@test.com` - Non-retryable error
- Any other email - Random success/3DS
---
## Key Concepts
### Payment Flow
1. **Your Backend**: Create payment with billing info via `/api/server/payment` (with your private API key)
2. **Frontend**: Initialize SDK with public API key
3. **Frontend**: Create CardElement with `paymentId` from backend
4. **Frontend**: Mount CardElement (displays card input form)
5. **Frontend**: User enters card details and clicks submit
6. **CardElement**: Tokenizes card data via PCI-compliant proxy
7. **CardElement**: Sends `paymentId + tokenIntentId` to `/sdk/payment/confirm`
8. **Backend**: Processes payment with Stripe using token
9. **CardElement**: Polls payment status (retry up to 3x, 2s intervals)
10. **Frontend**: Receives result via `onComplete` callback (success, 3DS challenge, or error)
11. **Handle 3DS**: If needed, redirect to challenge URL
12. **Confirm Success**: Customer returns to success URL
### Architecture
```
Backend (Private Key) Frontend (Public Key) Tokenization Proxy InflowPay API
| | | |
|--1. Create payment------>| | |
| (with billing info) | | |
|<--paymentId--------------| | |
| | | |
| CardElement | |
| |--2. Tokenize card------->| |
| |<--tokenIntentId----------| |
| | | |
| |--3. Confirm payment (paymentId + tokenIntentId)->|
| |<--processing started------------------------------|
| | | |
| |--4. wait 2.5s | |
| | | |
| |--5. Poll status (up to 3x)------------------->|
| |<--result (success/3DS/error)------------------|
```
### Payment Status
The SDK uses the `PaymentStatus` enum to track payment progress. Import and use it for type-safe status checking:
```typescript
import { PaymentStatus } from '@inflowpay/sdk';
```
**Status Flow:**
```
INITIATION
ā
CHECKOUT_PENDING (user is paying)
ā
CHECKOUT_SUCCESS (payment confirmed) ā SUCCESS starts here
ā
PAYMENT_RECEIVED (fiat funds received)
ā
PAYMENT_SUCCESS (crypto sent to wallet)
```
**All Status Values:**
| Status | Meaning | Is Success? | Is Failed? |
|--------|---------|-------------|------------|
| `INITIATION` | Payment created | No | No |
| `CHECKOUT_PENDING` | User is preparing payment | No | No |
| `CHECKOUT_SUCCESS` | User successfully paid | ā
**Yes** | No |
| `PAYMENT_RECEIVED` | Fiat funds received by provider | ā
**Yes** | No |
| `PAYMENT_SUCCESS` | Crypto sent to user wallet | ā
**Yes** | No |
| `CHECKOUT_CANCELED` | User canceled checkout | No | ā
Yes |
| `CANCELED` | Payment canceled | No | ā
Yes |
| `PAYMENT_FAILED` | Payment failed | No | ā
Yes |
> **Important:** Payment is considered successful from `CHECKOUT_SUCCESS` onwards. Always check for multiple success statuses:
>
> ```javascript
> if (result.status === PaymentStatus.CHECKOUT_SUCCESS ||
> result.status === PaymentStatus.PAYMENT_RECEIVED ||
> result.status === PaymentStatus.PAYMENT_SUCCESS) {
> // Handle success
> }
> ```
### 3D Secure (3DS) Flow
**The SDK automatically handles 3DS authentication for you!** When 3DS is required:
1. š The SDK automatically opens a modal overlay with the 3DS challenge page
2. š¤ User completes authentication inside the modal (without leaving your page)
3. ā
Modal auto-closes when authentication completes
4. š SDK automatically polls payment status until final result
5. š¢ Your `onComplete` callback receives the final payment result
**You don't need to handle 3DS redirects or callback URLs** - it's all managed by the SDK.
```javascript
const cardElement = sdk.createCardElement({
container: '#card-element',
paymentId: 'pay_123abc',
onComplete: (result) => {
// You'll only receive final results - 3DS is handled automatically
if (result.status === PaymentStatus.CHECKOUT_SUCCESS ||
result.status === PaymentStatus.PAYMENT_RECEIVED ||
result.status === PaymentStatus.PAYMENT_SUCCESS) {
// Payment successful!
window.location.href = '/success';
} else if (result.error) {
// Payment failed
console.error('Payment failed:', result.error.message);
if (result.error.retryable) {
alert('Payment failed. Please try a different card.');
}
}
}
});
```
**What happens during 3DS:**
- Modal appears with secure iframe showing bank's authentication page
- User enters their 3DS password/code
- Modal closes automatically on completion
- You receive the final payment status in your `onComplete` callback
**No manual redirect handling needed!** The entire 3DS flow is seamless and automatic.
## API Reference
### PaymentSDK
#### Constructor
```typescript
new PaymentSDK(config: SDKConfig)
```
**SDKConfig:**
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `apiKey` | `string` | Yes | Your InflowPay public API key |
| `environment` | `'sandbox' \| 'production'` | No | Environment (default: `'sandbox'`) |
| `apiUrl` | `string` | No | Custom API URL (for testing) |
| `tokenizationProxyUrl` | `string` | No | URL for card tokenization proxy |
| `tokenizationProxyKey` | `string` | No | API key for tokenization proxy |
| `timeout` | `number` | No | Request timeout in ms (default: 30000) |
| `debug` | `boolean` | No | Enable debug logging (default: `false`) |
#### Methods
##### `createCardElement(options: CardElementOptions): CardElement`
Creates a new CardElement instance.
---
### CardElement
The CardElement is a pre-built payment form that handles card input, validation, tokenization, and payment submission.
#### Options
```typescript
interface CardElementOptions {
container: string | HTMLElement; // Where to mount the element
paymentId: string; // Payment ID from your backend
// Optional customization
buttonText?: string; // Custom button text (default: "Complete Payment")
buttonStyle?: Partial<CSSStyleDeclaration>; // Custom button styles
style?: {
base?: CSSProperties; // Default input state
invalid?: CSSProperties; // Invalid input state
complete?: CSSProperties; // Valid input state
};
placeholders?: {
cardNumber?: string;
expiry?: string;
cvc?: string;
};
// Callbacks
onChange?: (state: CardElementState) => void;
onComplete?: (result: PaymentResult) => void;
onError?: (error: PaymentError) => void;
}
```
#### Customization
The CardElement is highly customizable to match your brand and design system.
##### Input Field Styling
Customize the appearance of card input fields with CSS properties:
```javascript
const cardElement = sdk.createCardElement({
container: '#card-element',
paymentId: 'pay_123',
style: {
base: {
color: '#333333',
fontSize: '15px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto',
fontWeight: '400',
letterSpacing: '0.025em',
'::placeholder': {
color: '#999999'
}
},
invalid: {
color: '#ef4444', // Red text for invalid input
},
complete: {
color: '#10b981', // Green text when valid
}
}
});
```
**Available CSS Properties:**
- `color` - Text color
- `fontSize` - Font size (e.g., `'16px'`, `'1rem'`)
- `fontFamily` - Font family
- `fontWeight` - Font weight (e.g., `'400'`, `'600'`, `'bold'`)
- `fontStyle` - Font style (e.g., `'normal'`, `'italic'`)
- `textDecoration` - Text decoration
- `letterSpacing` - Letter spacing
- `lineHeight` - Line height
- `'::placeholder'` - Placeholder text styling
##### Button Customization
Customize the submit button with any CSS properties:
```javascript
const cardElement = sdk.createCardElement({
container: '#card-element',
paymentId: 'pay_123',
buttonText: 'Pay with card',
buttonStyle: {
backgroundColor: '#000000',
color: '#ffffff',
fontSize: '15px',
fontWeight: '500',
padding: '16px',
borderRadius: '6px',
border: 'none',
width: '100%',
cursor: 'pointer',
transition: 'background-color 0.2s ease',
// ... any other CSS property!
}
});
```
The `buttonStyle` accepts **any valid CSS property** from `CSSStyleDeclaration`, giving you complete control over button appearance.
##### Placeholder Text
Customize input placeholder text:
```javascript
const cardElement = sdk.createCardElement({
container: '#card-element',
paymentId: 'pay_123',
placeholders: {
cardNumber: '1234 5678 9012 3456',
expiry: 'MM/YY',
cvc: 'CVV'
}
});
```
##### Complete Customization Example
Here's a fully customized CardElement matching a black/white design system:
```javascript
const cardElement = sdk.createCardElement({
container: '#card-element',
paymentId: payment.paymentId,
buttonText: 'Pay with card',
// Input styling
style: {
base: {
fontSize: '15px',
color: '#333333',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif',
'::placeholder': {
color: '#999999',
},
},
invalid: {
color: '#991b1b',
},
},
// Button styling
buttonStyle: {
backgroundColor: '#000000',
color: '#ffffff',
fontSize: '15px',
fontWeight: '500',
padding: '16px',
borderRadius: '6px',
border: 'none',
width: '100%',
cursor: 'pointer',
transition: 'background-color 0.2s ease',
},
// Callbacks
onChange: (state) => {
console.log('Card state:', state.complete);
// Enable/disable your own UI based on state
},
onComplete: (result) => {
if (result.status === 'CHECKOUT_SUCCESS') {
window.location.href = '/success';
}
},
onError: (error) => {
console.error('Payment error:', error.message);
}
});
cardElement.mount();
```
**What's NOT Customizable:**
- HTML structure and field layout (card number, expiry, CVC arrangement)
- Validation logic and security features
- "Secured by Inflowpay" disclaimer (always displayed for compliance)
#### Methods
##### `mount(): void`
Mounts the CardElement to the DOM.
```javascript
cardElement.mount();
```
**Note:** Once mounted, the CardElement automatically handles card input, validation, and submission. No additional methods need to be called.
##### `destroy(): void`
Clears all card input fields.
```javascript
cardElement.clear();
```
##### `destroy(): void`
Cleanup method to release resources.
```javascript
cardElement.destroy();
```
## Error Handling
The SDK automatically handles error mapping and provides user-friendly error messages. You receive errors through two callbacks:
### onComplete Callback
The `onComplete` callback receives the final payment result, which may include an error:
```javascript
const cardElement = sdk.createCardElement({
container: '#card-element',
paymentId: 'pay_123abc',
onComplete: (result) => {
// Check for success
if (result.status === 'CHECKOUT_SUCCESS' ||
result.status === 'PAYMENT_RECEIVED' ||
result.status === 'PAYMENT_SUCCESS') {
window.location.href = '/success';
}
// Check for errors
else if (result.error) {
if (result.error.retryable) {
// User can retry with different card
showError(`${result.error.message}\n\nYou can try again with a different card.`);
} else {
// Non-retryable error
showError(`${result.error.message}\n\nPlease contact support if this persists.`);
}
}
}
});
```
### onError Callback
The `onError` callback handles SDK-level errors (network issues, validation, etc.):
```javascript
const cardElement = sdk.createCardElement({
container: '#card-element',
paymentId: 'pay_123abc',
onError: (error) => {
console.error('SDK error:', error.code, error.message);
showError(error.message);
}
});
```
### What the SDK Handles Automatically
ā
**Automatic error mapping**: Raw API error codes are converted to user-friendly messages
ā
**Retryability detection**: All errors include a `retryable` boolean flag
ā
**Payment status polling**: Errors from failed payments are detected during status checks
ā
**Multiple error sources**: Checks both `payment.error` and `payment.lastDepositAttempt.error`
ā
**Stripe error codes**: Handles Stripe-specific errors like `card_declined`, `test_mode_live_card`, etc.
### Error Object Structure
```typescript
interface PaymentError {
code: string; // Error code (e.g., 'card_declined')
message: string; // User-friendly message (e.g., 'Your card was declined...')
retryable: boolean; // Whether user can retry with different card
statusCode?: number; // HTTP status code (if from API)
details?: any; // Raw error details (only in debug mode)
}
```
### Common Error Codes
**Retryable errors** (user can try again):
- `card_declined` - Card was declined by the bank
- `insufficient_funds` - Card has insufficient funds
- `invalid_card_number` - Invalid card number entered
- `invalid_expiry` - Invalid expiration date
- `invalid_cvc` - Invalid security code
- `test_mode_live_card` - Real card used in test mode
- `network_error` - Network connection issue
**Non-retryable errors** (payment failed permanently):
- `PAYMENT_FAILED` - Payment processing failed
- `PAYMENT_NOT_FOUND` - Payment session expired
- `THREE_DS_FAILED` - 3D Secure authentication failed
## Testing
Use the mock server for local development:
```bash
npm run mock-server
```
Then configure the SDK for sandbox environment:
```javascript
const sdk = new PaymentSDK({
apiKey: 'inflow_pub_dev_test_key',
environment: 'sandbox',
debug: true,
});
```
**Test scenarios by email:**
| Email Pattern | Result |
|--------------|--------|
| `decline@test.com` | Card declined (retryable) |
| `insufficient@test.com` | Insufficient funds (retryable) |
| `3ds@test.com` | Requires 3D Secure challenge |
| `error@test.com` | Non-retryable error |
| Any other email | 70% success, 30% 3DS |
## Browser Support
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
## Security
- Card data is tokenized via secure proxy before payment submission
- PCI DSS compliance through tokenization
- All API requests over HTTPS
- Input validation and sanitization
## Support
- Email: support@inflowpay.com
## License
MIT License - see [LICENSE](./LICENSE) file for details.