UNPKG

@inflowpay/sdk

Version:

Minimal JavaScript/TypeScript SDK providing a pre-built CardElement widget for InflowPay payment processing

732 lines (576 loc) • 21.3 kB
# 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.