Skip to main content

DRP JavaScript SDK

The official JavaScript SDK for integrating the Digital Receipt Protocol into Node.js applications, web apps, and React Native mobile apps.

Installation

npm install @drp/sdk
Requirements:
  • Node.js 16 or higher
  • TypeScript 4.5+ (optional, but recommended)

Quick Start

Merchant Integration (Server-Side)

const DRP = require('@drp/sdk');
// or ES6: import DRP from '@drp/sdk';

// Initialize client
const client = new DRP.Client({
  apiKey: process.env.DRP_API_KEY,
  issuerDomain: 'issuer-bank.com',
  environment: 'production', // or 'sandbox'
  privateKeyPath: './merchant-private-key.pem' // for signing
});

// Complete workflow: Get key, encrypt, send receipt
async function sendReceipt(cardToken, transactionId, receiptData) {
  try {
    // 1. Get customer's public key
    const keyData = await client.publicKeys.get(cardToken, {
      transactionId: transactionId
    });
    
    // 2. Encrypt and send receipt (SDK handles signing automatically)
    const result = await client.receipts.send({
      receiptId: `rcpt_${Date.now()}`,
      receipt: receiptData, // JSON-LD receipt object
      customerPublicKey: keyData.public_key,
      customerPublicKeyId: keyData.key_id,
      cardLastFour: cardToken.slice(-4),
      transactionId: transactionId,
      merchantId: 'mch_coffee_shop_001',
      amount: receiptData.totalPaymentDue.price,
      currency: receiptData.totalPaymentDue.priceCurrency
    });
    
    console.log('Receipt sent:', result.delivery_confirmation);
    return result;
    
  } catch (error) {
    if (error.code === 'customer_not_enrolled') {
      console.log('Customer not enrolled - skip DRP receipt');
      return null;
    }
    throw error;
  }
}

User Integration (Client-Side)

import DRP from '@drp/sdk';

// Initialize client with user OAuth token
const client = new DRP.UserClient({
  accessToken: userSession.oauthToken,
  issuerDomain: 'your-bank.com'
});

// Fetch and decrypt receipts
async function getUserReceipts(startDate, endDate) {
  // Fetch encrypted receipts
  const response = await client.receipts.list({
    startDate: startDate,
    endDate: endDate,
    limit: 50
  });
  
  // Note: Decryption requires private key from Secure Enclave (iOS/Android)
  // For web apps, users must import their private key
  const decryptedReceipts = await Promise.all(
    response.receipts.map(async (receipt) => {
      const decrypted = await client.crypto.decrypt(
        receipt.encrypted_receipt,
        privateKey // User's private key
      );
      
      return {
        ...receipt,
        data: JSON.parse(decrypted)
      };
    })
  );
  
  return decryptedReceipts;
}

Configuration

Client Options

interface ClientOptions {
  // Required
  apiKey: string;              // Merchant API key
  issuerDomain: string;        // Card issuer's domain
  
  // Optional
  environment?: 'production' | 'sandbox'; // Default: 'production'
  privateKeyPath?: string;     // Path to merchant private key file
  privateKey?: string;         // Or provide key directly (PEM format)
  timeout?: number;            // Request timeout in ms (default: 30000)
  retries?: number;            // Max retry attempts (default: 3)
  logLevel?: 'debug' | 'info' | 'warn' | 'error'; // Default: 'info'
}

Environment Variables

# .env file
DRP_API_KEY=mch_live_abc123def456
DRP_ISSUER_DOMAIN=issuer-bank.com
DRP_ENVIRONMENT=production
DRP_PRIVATE_KEY_PATH=./merchant-private-key.pem
require('dotenv').config();

const client = new DRP.Client({
  apiKey: process.env.DRP_API_KEY,
  issuerDomain: process.env.DRP_ISSUER_DOMAIN,
  environment: process.env.DRP_ENVIRONMENT,
  privateKeyPath: process.env.DRP_PRIVATE_KEY_PATH
});

API Reference

Client Methods

publicKeys.get(cardToken, options?)

Retrieve customer’s public key for encryption.
const keyData = await client.publicKeys.get('tok_card_abc123', {
  transactionId: 'txn_auth_456'
});

console.log(keyData.public_key);  // PEM-formatted public key
console.log(keyData.key_id);      // Key identifier
console.log(keyData.expires_at);  // Expiration timestamp
Returns: Promise<PublicKeyData>

receipts.send(params)

Encrypt and send a receipt to the card issuer.
const result = await client.receipts.send({
  receiptId: 'rcpt_unique_123',
  receipt: receiptJsonLd,       // JSON-LD receipt object
  customerPublicKey: keyData.public_key,
  customerPublicKeyId: keyData.key_id,
  cardLastFour: '1234',
  transactionId: 'txn_auth_456',
  merchantId: 'mch_coffee_shop_001',
  amount: 12.50,
  currency: 'USD'
});

console.log(result.delivery_confirmation);
console.log(result.issuer_signature);
Returns: Promise<ReceiptResponse>

receipts.list(filters)

Retrieve user’s receipts (User Client only).
const response = await userClient.receipts.list({
  startDate: '2025-11-01',
  endDate: '2025-12-07',
  merchantId: 'mch_coffee_shop_001',
  limit: 20,
  offset: 0
});

console.log(response.receipts);
console.log(response.pagination);
Returns: Promise<ReceiptsListResponse>

merchants.register(data)

Register merchant account (one-time setup).
const registration = await DRP.register({
  registrationKey: 'reg_key_xyz789',
  issuerDomain: 'issuer-bank.com',
  merchantId: 'mch_coffee_shop_001',
  merchantName: "Joe's Coffee Shop",
  contactEmail: '[email protected]',
  callbackUrl: 'https://coffeeshop.com/drp/webhook',
  publicKey: publicKeyPem,
  businessAddress: {
    street: '123 Main St',
    city: 'Seattle',
    state: 'WA',
    postalCode: '98101',
    country: 'US'
  }
});

console.log(registration.api_key);        // Save securely!
console.log(registration.webhook_secret);  // Save securely!
Returns: Promise<RegistrationResponse>

Crypto Utilities

crypto.encrypt(data, publicKey)

Encrypt data with RSA-2048-OAEP.
const encrypted = await client.crypto.encrypt(
  JSON.stringify(receiptData),
  customerPublicKey
);

crypto.decrypt(encryptedData, privateKey)

Decrypt data with private key.
const decrypted = await client.crypto.decrypt(
  encryptedBase64,
  userPrivateKey
);
const receiptData = JSON.parse(decrypted);

crypto.sign(data, privateKey)

Sign data with RSA-SHA256.
const signature = await client.crypto.sign(
  JSON.stringify(receiptData),
  merchantPrivateKey
);

crypto.verify(data, signature, publicKey)

Verify signature.
const isValid = await client.crypto.verify(
  JSON.stringify(receiptData),
  signature,
  merchantPublicKey
);

Error Handling

const { DRPError, RateLimitError, AuthenticationError } = require('@drp/sdk');

try {
  await client.receipts.send(receiptData);
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
    await sleep(error.retryAfter * 1000);
    // Retry logic
  } else if (error instanceof AuthenticationError) {
    console.error('Invalid API key');
  } else if (error.code === 'customer_not_enrolled') {
    // Customer hasn't enabled DRP - skip
  } else {
    console.error('DRP Error:', error.message);
    throw error;
  }
}

Error Types

  • DRPError - Base error class
  • AuthenticationError - Invalid API key
  • RateLimitError - Rate limit exceeded
  • ValidationError - Invalid request parameters
  • NetworkError - Network/timeout issues
  • EncryptionError - Encryption/decryption failed
  • SignatureError - Signature verification failed

TypeScript Support

Full TypeScript definitions included:
import DRP, { 
  Client, 
  PublicKeyData, 
  ReceiptResponse,
  SendReceiptParams 
} from '@drp/sdk';

const client: Client = new DRP.Client({
  apiKey: process.env.DRP_API_KEY!,
  issuerDomain: 'issuer-bank.com'
});

const keyData: PublicKeyData = await client.publicKeys.get(cardToken);

const params: SendReceiptParams = {
  receiptId: 'rcpt_123',
  receipt: receiptJsonLd,
  customerPublicKey: keyData.public_key,
  // ... TypeScript autocomplete for all fields
};

const response: ReceiptResponse = await client.receipts.send(params);

Testing

Mock Client

const { MockClient } = require('@drp/sdk/testing');

const mockClient = new MockClient();

// Mock responses
mockClient.publicKeys.get.mockResolvedValue({
  public_key: 'mock-public-key-pem',
  key_id: 'pk_mock_123',
  algorithm: 'RSA-2048-OAEP',
  expires_at: '2026-12-07T00:00:00Z'
});

mockClient.receipts.send.mockResolvedValue({
  status: 'accepted',
  receipt_id: 'rcpt_123',
  delivery_confirmation: 'conf_mock_456'
});

// Use in tests
const result = await mockClient.receipts.send(params);
expect(result.status).toBe('accepted');

Examples

Express.js Integration

const express = require('express');
const DRP = require('@drp/sdk');

const app = express();
const drpClient = new DRP.Client({
  apiKey: process.env.DRP_API_KEY,
  issuerDomain: 'issuer-bank.com',
  privateKeyPath: './keys/merchant-private.pem'
});

app.post('/checkout', async (req, res) => {
  const { cardToken, transactionId, receipt } = req.body;
  
  try {
    // Get public key
    const keyData = await drpClient.publicKeys.get(cardToken);
    
    // Send encrypted receipt
    const result = await drpClient.receipts.send({
      receiptId: `rcpt_${Date.now()}`,
      receipt: receipt,
      customerPublicKey: keyData.public_key,
      customerPublicKeyId: keyData.key_id,
      cardLastFour: cardToken.slice(-4),
      transactionId: transactionId,
      merchantId: process.env.MERCHANT_ID,
      amount: receipt.totalPaymentDue.price,
      currency: 'USD'
    });
    
    res.json({ success: true, confirmation: result.delivery_confirmation });
  } catch (error) {
    if (error.code === 'customer_not_enrolled') {
      // Customer not enrolled - that's ok
      res.json({ success: true, drp: false });
    } else {
      console.error('DRP error:', error);
      res.status(500).json({ error: 'Failed to send receipt' });
    }
  }
});

app.listen(3000);

React Native Integration

import DRP from '@drp/sdk';
import AsyncStorage from '@react-native-async-storage/async-storage';

// Client-side for banking apps
const userClient = new DRP.UserClient({
  accessToken: await AsyncStorage.getItem('oauth_token'),
  issuerDomain: 'your-bank.com'
});

// Fetch receipts
const receipts = await userClient.receipts.list({
  startDate: '2025-11-01',
  limit: 50
});

// Display in UI
receipts.receipts.forEach(receipt => {
  console.log(`${receipt.merchant_name}: $${receipt.amount}`);
});

Webhooks

Handle webhook events from issuers:
const express = require('express');
const DRP = require('@drp/sdk');

const app = express();
app.use(express.json());

app.post('/drp/webhook', (req, res) => {
  const signature = req.headers['x-drp-signature'];
  const webhookSecret = process.env.DRP_WEBHOOK_SECRET;
  
  // Verify webhook signature
  const isValid = DRP.webhooks.verify(
    req.body,
    signature,
    webhookSecret
  );
  
  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Handle event
  const { event, receipt_id, details } = req.body;
  
  switch (event) {
    case 'receipt.delivered':
      console.log(`Receipt ${receipt_id} delivered`);
      break;
    case 'receipt.disputed':
      console.log(`Receipt ${receipt_id} disputed:`, details.dispute_reason);
      // Handle dispute
      break;
  }
  
  res.json({ received: true });
});

Best Practices

Never commit API keys to version control. Use environment variables or secret management systems (AWS Secrets Manager, HashiCorp Vault, etc.).
Always wrap DRP calls in try/catch. Handle customer_not_enrolled as a normal case - not all customers will have DRP enabled.
The SDK has built-in retries, but implement additional retry logic at the application level for critical workflows.
Public keys are valid for months. Cache them by card token to reduce API calls and improve performance.
All SDK methods return Promises. Use async/await for cleaner code and better error handling.
Set logLevel: 'debug' during development to see detailed request/response logs. Use ‘error’ in production.

Resources


Changelog

v1.0.0 (Latest)

  • Initial stable release
  • Full support for DRP API v1
  • TypeScript definitions included
  • React Native compatibility
  • Webhook verification helpers

v0.9.0 (Beta)

  • Beta release for testing
  • Core merchant and user APIs
See full changelog on GitHub.