Skip to main content

DRP Python SDK

The official Python SDK for integrating the Digital Receipt Protocol into Python applications, including Django, Flask, FastAPI, and custom POS systems.

Installation

pip install drp-sdk --break-system-packages
Requirements:
  • Python 3.8 or higher
  • cryptography library (auto-installed)

Quick Start

Merchant Integration (Server-Side)

from drp_sdk import Client
import os

# Initialize client
client = Client(
    api_key=os.environ['DRP_API_KEY'],
    issuer_domain='issuer-bank.com',
    environment='production',  # or 'sandbox'
    private_key_path='./merchant-private-key.pem'
)

# Complete workflow: Get key, encrypt, send receipt
async def send_receipt(card_token, transaction_id, receipt_data):
    try:
        # 1. Get customer's public key
        key_data = client.public_keys.get(
            card_token,
            transaction_id=transaction_id
        )
        
        # 2. Encrypt and send receipt (SDK handles signing automatically)
        result = client.receipts.send({
            'receipt_id': f'rcpt_{int(time.time())}',
            'receipt': receipt_data,  # Dict with JSON-LD receipt
            'customer_public_key': key_data['public_key'],
            'customer_public_key_id': key_data['key_id'],
            'card_last_four': card_token[-4:],
            'transaction_id': transaction_id,
            'merchant_id': 'mch_coffee_shop_001',
            'amount': receipt_data['totalPaymentDue']['price'],
            'currency': receipt_data['totalPaymentDue']['priceCurrency']
        })
        
        print(f"Receipt sent: {result['delivery_confirmation']}")
        return result
        
    except CustomerNotEnrolledError:
        print('Customer not enrolled - skip DRP receipt')
        return None

Django Integration

# views.py
from django.http import JsonResponse
from drp_sdk import Client
from drp_sdk.exceptions import DRPError

drp_client = Client(
    api_key=settings.DRP_API_KEY,
    issuer_domain='issuer-bank.com',
    private_key_path=settings.DRP_PRIVATE_KEY_PATH
)

def checkout(request):
    card_token = request.POST.get('card_token')
    transaction_id = request.POST.get('transaction_id')
    
    # Process payment first...
    
    # Then send DRP receipt
    try:
        key_data = drp_client.public_keys.get(card_token)
        
        receipt = drp_client.receipts.send({
            'receipt_id': f'rcpt_{order.id}',
            'receipt': build_receipt_data(order),
            'customer_public_key': key_data['public_key'],
            'customer_public_key_id': key_data['key_id'],
            'card_last_four': card_token[-4:],
            'transaction_id': transaction_id,
            'merchant_id': settings.MERCHANT_ID,
            'amount': float(order.total),
            'currency': 'USD'
        })
        
        return JsonResponse({
            'success': True,
            'drp_confirmation': receipt['delivery_confirmation']
        })
        
    except DRPError as e:
        logger.error(f'DRP error: {e}')
        # Don't fail checkout if DRP fails
        return JsonResponse({'success': True, 'drp': False})

Flask Integration

# app.py
from flask import Flask, request, jsonify
from drp_sdk import Client

app = Flask(__name__)

drp_client = Client(
    api_key=app.config['DRP_API_KEY'],
    issuer_domain='issuer-bank.com',
    private_key_path='./keys/merchant-private.pem'
)

@app.route('/checkout', methods=['POST'])
def checkout():
    data = request.json
    
    try:
        # Get public key
        key_data = drp_client.public_keys.get(data['card_token'])
        
        # Send receipt
        result = drp_client.receipts.send({
            'receipt_id': f"rcpt_{data['order_id']}",
            'receipt': data['receipt'],
            'customer_public_key': key_data['public_key'],
            'customer_public_key_id': key_data['key_id'],
            'card_last_four': data['card_token'][-4:],
            'transaction_id': data['transaction_id'],
            'merchant_id': app.config['MERCHANT_ID'],
            'amount': data['amount'],
            'currency': 'USD'
        })
        
        return jsonify({'success': True, 'confirmation': result['delivery_confirmation']})
        
    except Exception as e:
        app.logger.error(f'DRP error: {e}')
        return jsonify({'success': True, 'drp': False})

FastAPI Integration

# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from drp_sdk import Client
from drp_sdk.exceptions import DRPError

app = FastAPI()

drp_client = Client(
    api_key=settings.DRP_API_KEY,
    issuer_domain='issuer-bank.com',
    private_key_path='./keys/merchant-private.pem'
)

class CheckoutRequest(BaseModel):
    card_token: str
    transaction_id: str
    receipt: dict
    amount: float

@app.post('/checkout')
async def checkout(request: CheckoutRequest):
    try:
        # Get public key
        key_data = await drp_client.public_keys.get_async(request.card_token)
        
        # Send receipt
        result = await drp_client.receipts.send_async({
            'receipt_id': f'rcpt_{int(time.time())}',
            'receipt': request.receipt,
            'customer_public_key': key_data['public_key'],
            'customer_public_key_id': key_data['key_id'],
            'card_last_four': request.card_token[-4:],
            'transaction_id': request.transaction_id,
            'merchant_id': settings.MERCHANT_ID,
            'amount': request.amount,
            'currency': 'USD'
        })
        
        return {'success': True, 'confirmation': result['delivery_confirmation']}
        
    except DRPError as e:
        # Log but don't fail checkout
        logger.error(f'DRP error: {e}')
        return {'success': True, 'drp': False}

Configuration

Client Options

from drp_sdk import Client

client = Client(
    # Required
    api_key='mch_live_abc123def456',
    issuer_domain='issuer-bank.com',
    
    # Optional
    environment='production',  # or 'sandbox'
    private_key_path='./merchant-private-key.pem',  # Path to key file
    private_key=None,  # Or provide key directly (PEM string)
    timeout=30.0,  # Request timeout in seconds
    max_retries=3,  # Max retry attempts
    log_level='INFO'  # DEBUG, INFO, WARNING, ERROR
)

Environment Variables

# .env
DRP_API_KEY=mch_live_abc123def456
DRP_ISSUER_DOMAIN=issuer-bank.com
DRP_ENVIRONMENT=production
DRP_PRIVATE_KEY_PATH=./merchant-private-key.pem
# Load with python-dotenv
from dotenv import load_dotenv
import os

load_dotenv()

client = Client(
    api_key=os.getenv('DRP_API_KEY'),
    issuer_domain=os.getenv('DRP_ISSUER_DOMAIN'),
    environment=os.getenv('DRP_ENVIRONMENT'),
    private_key_path=os.getenv('DRP_PRIVATE_KEY_PATH')
)

API Reference

Client Methods

public_keys.get(card_token, transaction_id=None)

Retrieve customer’s public key for encryption.
key_data = client.public_keys.get(
    'tok_card_abc123',
    transaction_id='txn_auth_456'
)

print(key_data['public_key'])  # PEM-formatted public key
print(key_data['key_id'])      # Key identifier
print(key_data['expires_at'])  # Expiration timestamp
Returns: dict with public key data

receipts.send(params)

Encrypt and send a receipt to the card issuer.
result = client.receipts.send({
    'receipt_id': 'rcpt_unique_123',
    'receipt': receipt_json_ld,
    'customer_public_key': key_data['public_key'],
    'customer_public_key_id': key_data['key_id'],
    'card_last_four': '1234',
    'transaction_id': 'txn_auth_456',
    'merchant_id': 'mch_coffee_shop_001',
    'amount': 12.50,
    'currency': 'USD'
})

print(result['delivery_confirmation'])
print(result['issuer_signature'])
Returns: dict with receipt response

receipts.list(filters) - User Client Only

Retrieve user’s receipts.
from drp_sdk import UserClient

user_client = UserClient(
    access_token=user_oauth_token,
    issuer_domain='your-bank.com'
)

response = user_client.receipts.list(
    start_date='2025-11-01',
    end_date='2025-12-07',
    merchant_id='mch_coffee_shop_001',
    limit=20,
    offset=0
)

print(response['receipts'])
print(response['pagination'])
Returns: dict with receipts list and pagination

merchants.register(data) - Class Method

Register merchant account (one-time setup).
from drp_sdk import Client

registration = Client.register(
    registration_key='reg_key_xyz789',
    issuer_domain='issuer-bank.com',
    merchant_id='mch_coffee_shop_001',
    merchant_name="Joe's Coffee Shop",
    contact_email='[email protected]',
    callback_url='https://coffeeshop.com/drp/webhook',
    public_key=public_key_pem,
    business_address={
        'street': '123 Main St',
        'city': 'Seattle',
        'state': 'WA',
        'postal_code': '98101',
        'country': 'US'
    }
)

print(registration['api_key'])        # Save securely!
print(registration['webhook_secret'])  # Save securely!
Returns: dict with registration response

Async Support (FastAPI, asyncio)

All methods have async equivalents:
# Async methods
key_data = await client.public_keys.get_async(card_token)
result = await client.receipts.send_async(params)
receipts = await user_client.receipts.list_async(filters)

Crypto Utilities

crypto.encrypt(data, public_key)

encrypted = client.crypto.encrypt(
    json.dumps(receipt_data),
    customer_public_key
)

crypto.decrypt(encrypted_data, private_key)

decrypted = client.crypto.decrypt(
    encrypted_base64,
    user_private_key
)
receipt_data = json.loads(decrypted)

crypto.sign(data, private_key)

signature = client.crypto.sign(
    json.dumps(receipt_data),
    merchant_private_key
)

crypto.verify(data, signature, public_key)

is_valid = client.crypto.verify(
    json.dumps(receipt_data),
    signature,
    merchant_public_key
)

Error Handling

from drp_sdk.exceptions import (
    DRPError,
    RateLimitError,
    AuthenticationError,
    CustomerNotEnrolledError,
    ValidationError
)

try:
    client.receipts.send(receipt_data)
except RateLimitError as e:
    print(f'Rate limited. Retry after {e.retry_after} seconds')
    time.sleep(e.retry_after)
    # Retry logic
except AuthenticationError:
    print('Invalid API key')
except CustomerNotEnrolledError:
    # Customer hasn't enabled DRP - skip
    pass
except ValidationError as e:
    print(f'Invalid data: {e.errors}')
except DRPError as e:
    print(f'DRP Error: {e}')
    raise

Exception Hierarchy

DRPError (base)
 AuthenticationError
 RateLimitError
 ValidationError
 NetworkError
 EncryptionError
 SignatureError

Testing

Mock Client

from drp_sdk.testing import MockClient

mock_client = MockClient()

# Mock responses
mock_client.public_keys.get.return_value = {
    'public_key': 'mock-public-key-pem',
    'key_id': 'pk_mock_123',
    'algorithm': 'RSA-2048-OAEP',
    'expires_at': '2026-12-07T00:00:00Z'
}

mock_client.receipts.send.return_value = {
    'status': 'accepted',
    'receipt_id': 'rcpt_123',
    'delivery_confirmation': 'conf_mock_456'
}

# Use in tests
result = mock_client.receipts.send(params)
assert result['status'] == 'accepted'

pytest Example

import pytest
from drp_sdk import Client
from drp_sdk.testing import MockClient

@pytest.fixture
def drp_client():
    return MockClient()

def test_send_receipt(drp_client):
    drp_client.receipts.send.return_value = {
        'status': 'accepted',
        'delivery_confirmation': 'conf_123'
    }
    
    result = drp_client.receipts.send({
        'receipt_id': 'test_123',
        # ... other params
    })
    
    assert result['status'] == 'accepted'
    drp_client.receipts.send.assert_called_once()

Webhooks

Handle webhook events from issuers:
from flask import Flask, request, jsonify
from drp_sdk import webhooks
import os

app = Flask(__name__)

@app.route('/drp/webhook', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-DRP-Signature')
    webhook_secret = os.getenv('DRP_WEBHOOK_SECRET')
    
    # Verify webhook signature
    is_valid = webhooks.verify(
        request.get_data(),
        signature,
        webhook_secret
    )
    
    if not is_valid:
        return jsonify({'error': 'Invalid signature'}), 401
    
    # Handle event
    event_data = request.json
    event = event_data['event']
    receipt_id = event_data['receipt_id']
    
    if event == 'receipt.delivered':
        print(f'Receipt {receipt_id} delivered')
    elif event == 'receipt.disputed':
        details = event_data['details']
        print(f'Receipt {receipt_id} disputed: {details["dispute_reason"]}')
        # Handle dispute
    
    return jsonify({'received': True})

Best Practices

Never hardcode API keys. Use environment variables or secret management (AWS Secrets Manager, HashiCorp Vault).
Always catch CustomerNotEnrolledError - not all customers have DRP enabled. This is normal and shouldn’t fail the checkout.
FastAPI is async-native. Use get_async() and send_async() methods for better performance.
Public keys are valid for months. Cache them with Redis or in-memory cache to reduce API calls.
from functools import lru_cache

@lru_cache(maxsize=1000)
def get_cached_public_key(card_token):
    return client.public_keys.get(card_token)
DRP errors shouldn’t prevent checkout. Log the error, fall back to email receipts, and investigate later.

Resources


Changelog

v1.0.0 (Latest)

  • Initial stable release
  • Full support for DRP API v1
  • Async/await support
  • Django, Flask, FastAPI examples
  • Type hints throughout

v0.9.0 (Beta)

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