Skip to main content

TumiPay Card Payment SDK

JavaScript/TypeScript SDK for secure credit and debit card tokenization.

Installation

npm install tumipay-cards-js

Quick Start

import { TumiPayCardPaymentSDK } from 'tumipay-cards-js';

// Create instance
const sdk = new TumiPayCardPaymentSDK({
  baseUrl: 'https://api.tumipay.co/v1',
  apiKey: 'your-api-key',
  solutionId: 'CARD_PAYMENT_SOLUTION_V1',
  environment: 'test',
  currency: 'COP'
});

// Initialize
await sdk.initialize({ merchantId: 'your-merchant-id' });

// Validate card
const result = await sdk.validateCard({
  cardNumber: '4532015112830366',
  cvv: '123',
  expirationMonth: '12',
  expirationYear: '2025',
  cardHolderName: 'John Doe'
});

// Create token
const token = await sdk.createToken({
  cardNumber: '4532015112830366',
  cvv: '123',
  expirationMonth: '12',
  expirationYear: '2025',
  cardHolderName: 'John Doe'
});

SDK Methods

1. initialize(options: InitializeOptions): Promise<void>

Initializes the SDK with the merchant ID. This method automatically retrieves all necessary configurations from the server.

Request

interface InitializeOptions {
  merchantId: string;  // Unique merchant ID
}
Field Specifications:
FieldTypeLengthRequiredDescription
merchantIdstring-✅ YesUnique merchant identifier (UUID format)
Example:
await sdk.initialize({
  merchantId: 'merchant-123-abc'
});

Response

Promise<void>  // Resolves when initialization is successful

Error Codes

CodeDescriptionSolution
MERCHANT_NOT_FOUNDMerchant ID does not existVerify the provided ID
INVALID_MERCHANT_IDInvalid merchant ID formatMust be a valid UUID
MERCHANT_INACTIVEMerchant disabled or inactiveContact support
CONFIG_RETRIEVAL_ERRORError retrieving configuration from serverCheck connectivity
MISSING_CREDENTIALSMissing credentials in server configurationContact support
NETWORK_ERRORConnection error with serverCheck your internet connection
SDK_ALREADY_INITIALIZEDSDK was already initializedNo need to initialize again
Error handling example:
try {
  await sdk.initialize({ merchantId: 'merchant-123' });
  console.log('SDK initialized successfully');
} catch (error) {
  switch (error.code) {
    case 'MERCHANT_NOT_FOUND':
      console.error('Merchant ID not found');
      break;
    case 'MERCHANT_INACTIVE':
      console.error('Merchant inactive, contact support');
      break;
    case 'NETWORK_ERROR':
      console.error('Connection error, try again');
      break;
    default:
      console.error('Unknown error:', error.message);
  }
}

2. validateCard(card: CardData): Promise<ResultValidation>

Validates card data without creating the token. Useful for real-time validation while the user enters data.

Request

interface CardData {
  cardNumber: string;        // Card number (13-19 digits)
  cvv: string;              // Security code (3-4 digits)
  expirationMonth: string;  // Expiration month (01-12)
  expirationYear: string;   // Expiration year (YY or YYYY)
  cardHolderName: string;   // Cardholder name (minimum 3 characters)
}
Field Specifications:
FieldTypeLengthRequiredDescription
cardNumberstring13-19 digits✅ YesCard number (must pass Luhn validation). Only numeric characters allowed.
cvvstring3-4 digits✅ YesCard security code. 3 digits for Visa/Mastercard, 4 digits for Amex. Only numeric characters.
expirationMonthstring2 characters✅ YesCard expiration month (01-12). Must be zero-padded (e.g., “01”, “12”).
expirationYearstring2 or 4 characters✅ YesCard expiration year. Accepts YY (e.g., “25”) or YYYY (e.g., “2025”) format.
cardHolderNamestringMin: 3 chars✅ YesCardholder full name as shown on the card. Must contain at least 3 characters.
Example:
const result = await sdk.validateCard({
  cardNumber: '4532015112830366',
  cvv: '123',
  expirationMonth: '12',
  expirationYear: '2025',
  cardHolderName: 'John Doe'
});

Response

interface ResultValidation {
  isValid: boolean;           // true if all data is valid
  errors?: ValidationError[]; // Array of errors (only if isValid is false)
  cardBrand?: string;        // Card brand: 'visa', 'mastercard', 'amex', etc.
}

interface ValidationError {
  field: string;      // Field with error: 'cardNumber', 'cvv', 'expirationMonth', etc.
  code: string;       // Error code
  message: string;    // Descriptive error message
}
Successful response:
{
  isValid: true,
  cardBrand: 'visa',
  errors: []
}
Response with errors:
{
  isValid: false,
  cardBrand: 'unknown',
  errors: [
    {
      field: 'cardNumber',
      code: 'INVALID_CARD_NUMBER',
      message: 'Card number is invalid'
    },
    {
      field: 'cvv',
      code: 'INVALID_CVV_LENGTH',
      message: 'CVV must be 3 or 4 digits'
    }
  ]
}

Error Codes by Field

cardNumber:
CodeDescription
CARD_NUMBER_REQUIREDCard number is required
INVALID_CARD_NUMBERInvalid card number (Luhn algorithm fails)
INVALID_CARD_LENGTHIncorrect length (must be 13-19 digits)
CARD_NUMBER_NOT_NUMERICOnly numbers allowed
UNSUPPORTED_CARD_BRANDUnsupported card brand
cvv:
CodeDescription
CVV_REQUIREDCVV is required
INVALID_CVV_LENGTHCVV must be 3 digits (Visa/MC) or 4 (Amex)
CVV_NOT_NUMERICCVV can only contain numbers
expirationMonth:
CodeDescription
EXPIRATION_MONTH_REQUIREDExpiration month is required
INVALID_EXPIRATION_MONTHInvalid month (must be 01-12)
EXPIRATION_MONTH_NOT_NUMERICMonth must be numeric
expirationYear:
CodeDescription
EXPIRATION_YEAR_REQUIREDExpiration year is required
INVALID_EXPIRATION_YEARInvalid year or incorrect format
CARD_EXPIREDCard is already expired
EXPIRATION_YEAR_TOO_FARExpiration year too far in future (max 20 years)
cardHolderName:
CodeDescription
CARDHOLDER_NAME_REQUIREDCardholder name is required
CARDHOLDER_NAME_TOO_SHORTName too short (minimum 3 characters)
CARDHOLDER_NAME_INVALIDInvalid characters in name
General:
CodeDescription
SDK_NOT_INITIALIZEDMust call initialize() before validating
VALIDATION_ERRORGeneral validation error
Handling example:
const result = await sdk.validateCard(cardData);

if (result.isValid) {
  console.log(`Valid ${result.cardBrand} card`);
  // Continue with createToken
} else {
  console.error('Validation errors:');
  result.errors?.forEach(error => {
    console.error(`  - ${error.field}: ${error.message}`);
  });
  // Show errors to user in form
}

3. createToken(request: TokenRequest): Promise<Token>

Creates a secure token for the card. This method automatically validates before tokenizing.

Request

interface TokenRequest {
  cardNumber: string;        // Card number (13-19 digits)
  cvv: string;              // Security code (3-4 digits)
  expirationMonth: string;  // Expiration month (01-12)
  expirationYear: string;   // Expiration year (YY or YYYY)
  cardHolderName: string;   // Cardholder name
}
Field Specifications:
FieldTypeLengthRequiredDescription
cardNumberstring13-19 digits✅ YesCard number (must pass Luhn validation). Only numeric characters allowed.
cvvstring3-4 digits✅ YesCard security code. 3 digits for Visa/Mastercard, 4 digits for Amex. Only numeric characters.
expirationMonthstring2 characters✅ YesCard expiration month (01-12). Must be zero-padded (e.g., “01”, “12”).
expirationYearstring2 or 4 characters✅ YesCard expiration year. Accepts YY (e.g., “25”) or YYYY (e.g., “2025”) format.
cardHolderNamestringMin: 3 chars✅ YesCardholder full name as shown on the card. Must contain at least 3 characters.
Example:
const token = await sdk.createToken({
  cardNumber: '4532015112830366',
  cvv: '123',
  expirationMonth: '12',
  expirationYear: '2025',
  cardHolderName: 'John Doe'
});

Response

interface Token {
  token: string;              // Secure card token
  brand: string;              // Brand: 'visa', 'mastercard', 'amex', etc.
  lastFourDigits: string;     // Last 4 digits of card
  bin: string;                // First 6 digits (Bank Identification Number)
  expirationMonth: string;    // Expiration month (MM format)
  expirationYear: string;     // Expiration year (YYYY format)
  cardHolderName: string;     // Cardholder name
  security?: SecurityInfo;    // Security information (optional)
}

interface SecurityInfo {
  requiresAuthentication: boolean;  // If additional authentication was required
  authenticationType?: '3DS' | 'OTP';  // Type of authentication used
  authenticationStatus: 'completed' | 'not_required' | 'failed';
}
Successful response:
{
  token: 'tok_1a2b3c4d5e6f7g8h9i0j',
  brand: 'visa',
  lastFourDigits: '0366',
  bin: '453201',
  expirationMonth: '12',
  expirationYear: '2025',
  cardHolderName: 'John Doe',
  security: {
    requiresAuthentication: false,
    authenticationStatus: 'not_required'
  }
}

Error Codes

Data Validation:
CodeDescription
VALIDATION_FAILEDData validation failed (includes details)
CARD_NUMBER_REQUIREDCard number missing
INVALID_CARD_NUMBERInvalid card number
CVV_REQUIREDCVV missing
INVALID_CVVInvalid CVV
CARD_EXPIREDCard expired
CARDHOLDER_NAME_REQUIREDCardholder name missing
Tokenization Errors:
CodeDescription
TOKENIZATION_FAILEDError creating token
KUSHKI_SERVICE_UNAVAILABLETokenization service unavailable
CARD_DECLINEDCard declined by issuer
CARD_BLOCKEDCard blocked
INSUFFICIENT_FUNDSInsufficient funds (online validation only)
DUPLICATE_TRANSACTIONDuplicate transaction
FRAUD_DETECTIONTransaction blocked by fraud detection
3DS/OTP Authentication Errors:
CodeDescription
AUTHENTICATION_REQUIREDCard requires 3DS or OTP authentication
AUTHENTICATION_FAILED3DS/OTP authentication failed
AUTHENTICATION_TIMEOUTAuthentication timeout
AUTHENTICATION_REJECTEDUser rejected authentication
AUTHENTICATION_CANCELLEDUser cancelled authentication
3DS_NOT_ENROLLEDCard not enrolled in 3D Secure
3DS_CHALLENGE_FAILED3D Secure challenge failed
OTP_INVALIDInvalid OTP code
OTP_EXPIREDOTP code expired
OTP_MAX_ATTEMPTSMaximum OTP attempts reached
Configuration Errors:
CodeDescription
SDK_NOT_INITIALIZEDSDK not initialized
MISSING_CONFIGURATIONRequired configuration missing
INVALID_MERCHANT_CONFIGInvalid merchant configuration
Network Errors:
CodeDescription
NETWORK_ERRORConnection error
TIMEOUT_ERRORTimeout error
SERVICE_UNAVAILABLEService temporarily unavailable
Error handling example:
try {
  const token = await sdk.createToken(cardData);
  
  console.log('Token created:', token.token);
  console.log(`   ${token.brand} **** ${token.lastFourDigits}`);
  
} catch (error) {
  switch (error.code) {
    // Validation errors
    case 'VALIDATION_FAILED':
      console.error('Invalid data:', error.details);
      break;
    case 'CARD_EXPIRED':
      console.error('Card expired');
      break;
    
    // Tokenization errors
    case 'CARD_DECLINED':
      console.error('Card declined by bank');
      break;
    case 'KUSHKI_SERVICE_UNAVAILABLE':
      console.error('Service unavailable, try later');
      break;
    case 'FRAUD_DETECTION':
      console.error('Transaction blocked for security');
      break;
    
    // 3DS/OTP authentication errors
    case 'AUTHENTICATION_FAILED':
      console.error('Could not authenticate card');
      break;
    case 'AUTHENTICATION_TIMEOUT':
      console.error('Authentication timeout');
      break;
    case '3DS_CHALLENGE_FAILED':
      console.error('3D Secure verification failed');
      break;
    case 'OTP_INVALID':
      console.error('Invalid OTP code');
      break;
    
    // Configuration errors
    case 'SDK_NOT_INITIALIZED':
      console.error('Must initialize SDK first');
      break;
    
    // Network errors
    case 'NETWORK_ERROR':
      console.error('Connection error, check your internet');
      break;
    case 'TIMEOUT_ERROR':
      console.error('Timeout, try again');
      break;
    
    default:
      console.error('Error:', error.message);
  }
}

4. validateOTP(request: ValidateOTPRequest): Promise<ValidateOTPResponse>

Validates the OTP (One-Time Password) code sent to the customer as part of the authentication process. This method is used when the transaction requires additional verification via temporary code.

Request

interface ValidateOTPRequest {
  otpValue: string;  // 3-6 digit OTP code sent to user
}
Field Specifications:
FieldTypeLengthRequiredDescription
otpValuestring3-6 digits✅ YesOne-Time Password code sent to the customer. Only numeric characters allowed.
Example:
const result = await sdk.validateOTP({
  otpValue: '123456'
});

Response

interface ValidateOTPResponse {
  code: string;      // Server response code
  message: string;   // Descriptive validation message
  result: boolean;   // true if OTP is valid, false otherwise
}
Successful response:
{
  code: 'OTP000',
  message: 'OTP validated successfully',
  result: true
}

Error Codes

OTP Validation:
CodeDescription
OTP_REQUIREDOTP code is required
INVALID_OTPIncorrect or invalid OTP code
OTP_EXPIREDOTP code has expired
OTP_MAX_ATTEMPTSMaximum number of attempts reached
OTP_NOT_FOUNDNo active OTP session found
OTP_ALREADY_USEDThis OTP code was already used
Format Validation Errors:
CodeDescription
VALIDATION_FAILEDData validation failed (includes details)
OTP_INVALID_FORMATInvalid OTP format (must be numeric)
OTP_INVALID_LENGTHIncorrect OTP code length
Configuration Errors:
CodeDescription
SDK_NOT_INITIALIZEDSDK not initialized
MISSING_CONFIGURATIONRequired configuration missing
INVALID_MERCHANT_CONFIGInvalid merchant configuration
Network Errors:
CodeDescription
NETWORK_ERRORConnection error
TIMEOUT_ERRORTimeout error
SERVICE_UNAVAILABLEService temporarily unavailable
Error handling example:
try {
  const result = await sdk.validateOTP({
    otpValue: '123456'
  });
  
  console.log('OTP validated successfully:', result.message);
  // Continue with payment process
  
} catch (error) {
  switch (error.code) {
    // OTP validation errors
    case 'INVALID_OTP':
      console.error('Incorrect OTP code, try again');
      break;
    case 'OTP_EXPIRED':
      console.error('OTP code has expired, request a new one');
      break;
    case 'OTP_MAX_ATTEMPTS':
      console.error('Too many failed attempts, request a new code');
      break;
    case 'OTP_ALREADY_USED':
      console.error('This code was already used');
      break;
    case 'OTP_NOT_FOUND':
      console.error('No active OTP session');
      break;
    
    // Format errors
    case 'VALIDATION_FAILED':
      console.error('Invalid data:', error.details);
      break;
    case 'OTP_INVALID_FORMAT':
      console.error('OTP code must be numeric');
      break;
    case 'OTP_INVALID_LENGTH':
      console.error('OTP code must be between 3 and 6 digits');
      break;
    
    // Configuration errors
    case 'SDK_NOT_INITIALIZED':
      console.error('Must initialize SDK first');
      break;
    
    // Network errors
    case 'NETWORK_ERROR':
      console.error('Connection error, check your internet');
      break;
    case 'TIMEOUT_ERROR':
      console.error('Timeout, try again');
      break;
    
    default:
      console.error('Error:', error.message);
  }
}

Authentication Handling

The SDK automatically and internally handles all 3DS (3D Secure) and OTP authentication processes when the card requires it. For the client:
  • The createToken() method performs the entire authentication flow transparently
  • If authentication is successful, you’ll receive the token normally
  • If it fails, you’ll receive a specific error (see error codes above)
You don’t need to implement anything additional for 3DS or OTP. The SDK handles:
  • Detecting if the card requires authentication
  • Executing the 3DS or OTP flow as appropriate
  • Validating authentication with the bank
  • Returning the final token or a descriptive error
Example:
try {
  // SDK handles 3DS/OTP internally
  const token = await sdk.createToken(cardData);
  
  console.log('Token created successfully:', token.token);
  // Proceed with payment
  
} catch (error) {
  // Handle authentication errors if they occur
  if (error.code === 'AUTHENTICATION_FAILED') {
    console.error('Authentication failed');
  } else if (error.code === '3DS_CHALLENGE_FAILED') {
    console.error('User did not complete 3DS verification');
  }
  // See full list of error codes above
}

Test Cards

For test environment, use these cards:
BrandNumberCVVResult
Visa4532015112830366123Approved
Visa4111111111111111123Approved
Visa4000000000000002123Declined
Mastercard5425233430109903123Approved
Mastercard5555555555554444123Approved
Amex3742454554001261234Approved
Valid dates: Any future month/year (e.g., 12/2025)

Complete Example

TypeScript

import { TumiPayCardPaymentSDK } from 'tumipay-cards-js';

const sdk = new TumiPayCardPaymentSDK({
  baseUrl: 'https://api.tumipay.co/v1',
  apiKey: process.env.TUMIPAY_API_KEY!,
  solutionId: 'CARD_PAYMENT_SOLUTION_V1',
  environment: 'test',
  currency: 'COP'
});

async function processPayment() {
  try {
    // 1. Initialize SDK
    await sdk.initialize({
      merchantId: process.env.MERCHANT_ID!
    });
    console.log('SDK initialized');

    const cardData = {
      cardNumber: '4532015112830366',
      cvv: '123',
      expirationMonth: '12',
      expirationYear: '2025',
      cardHolderName: 'John Doe'
    };

    // 2. Validate card (optional but recommended)
    const validation = await sdk.validateCard(cardData);
    
    if (!validation.isValid) {
      console.error('Validation failed:');
      validation.errors?.forEach(err => {
        console.error(`   ${err.field}: ${err.message}`);
      });
      return;
    }

    console.log(`Valid ${validation.cardBrand} card`);

    // 3. Create token
    const token = await sdk.createToken(cardData);
    
    console.log('Token created:', token.token);
    console.log(`   ${token.brand} **** ${token.lastFourDigits}`);
    console.log(`   Expires: ${token.expirationMonth}/${token.expirationYear}`);

    // Now use the token to process payment...
    
  } catch (error) {
    console.error('Error:', error.code, error.message);
  }
}

processPayment();

React with TypeScript

import { useState } from 'react';
import { TumiPayCardPaymentSDK } from 'tumipay-cards-js';

const sdk = new TumiPayCardPaymentSDK({
  baseUrl: 'https://api.tumipay.co/v1',
  apiKey: process.env.REACT_APP_TUMIPAY_API_KEY!,
  solutionId: 'CARD_PAYMENT_SOLUTION_V1',
  environment: 'test',
  currency: 'COP'
});

// Initialize once
await sdk.initialize({
  merchantId: process.env.REACT_APP_MERCHANT_ID!
});

function PaymentForm() {
  const [cardNumber, setCardNumber] = useState('');
  const [cvv, setCvv] = useState('');
  const [expirationMonth, setExpirationMonth] = useState('');
  const [expirationYear, setExpirationYear] = useState('');
  const [cardHolderName, setCardHolderName] = useState('');
  const [errors, setErrors] = useState<Record<string, string>>({});
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setErrors({});

    const cardData = {
      cardNumber,
      cvv,
      expirationMonth,
      expirationYear,
      cardHolderName
    };

    try {
      // Validate first
      const validation = await sdk.validateCard(cardData);
      
      if (!validation.isValid) {
        const newErrors: Record<string, string> = {};
        validation.errors?.forEach(err => {
          newErrors[err.field] = err.message;
        });
        setErrors(newErrors);
        setLoading(false);
        return;
      }

      // Create token
      const token = await sdk.createToken(cardData);
      
      console.log('Token:', token.token);
      alert('Payment successful!');
      
    } catch (error: any) {
      setErrors({ general: error.message });
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          type="text"
          placeholder="Card number"
          value={cardNumber}
          onChange={(e) => setCardNumber(e.target.value)}
        />
        {errors.cardNumber && <span className="error">{errors.cardNumber}</span>}
      </div>

      <div>
        <input
          type="text"
          placeholder="Cardholder name"
          value={cardHolderName}
          onChange={(e) => setCardHolderName(e.target.value)}
        />
        {errors.cardHolderName && <span className="error">{errors.cardHolderName}</span>}
      </div>

      <div>
        <input
          type="text"
          placeholder="MM"
          maxLength={2}
          value={expirationMonth}
          onChange={(e) => setExpirationMonth(e.target.value)}
        />
        <input
          type="text"
          placeholder="YYYY"
          maxLength={4}
          value={expirationYear}
          onChange={(e) => setExpirationYear(e.target.value)}
        />
        {errors.expirationMonth && <span className="error">{errors.expirationMonth}</span>}
        {errors.expirationYear && <span className="error">{errors.expirationYear}</span>}
      </div>

      <div>
        <input
          type="text"
          placeholder="CVV"
          maxLength={4}
          value={cvv}
          onChange={(e) => setCvv(e.target.value)}
        />
        {errors.cvv && <span className="error">{errors.cvv}</span>}
      </div>

      {errors.general && <div className="error">{errors.general}</div>}

      <button type="submit" disabled={loading}>
        {loading ? 'Processing...' : 'Pay'}
      </button>
    </form>
  );
}

export default PaymentForm;