Python Library (omise-python)
The omise-python library provides a Pythonic interface to the Omise API with support for Django and Flask, async operations, type hints, and comprehensive error handling.
Installationโ
Using pip (Recommended)โ
pip install omise
Using pip with requirements.txtโ
# requirements.txt
omise>=0.11.0
pip install -r requirements.txt
Using Poetryโ
poetry add omise
Requirementsโ
- Python 3.6 or higher (including Python 3.11+)
- requests library (installed automatically)
- Optional: aiohttp for async support
Quick Startโ
Basic Configurationโ
import omise
# Configure with your API keys
omise.api_secret = 'skey_test_123456789'
omise.api_version = '2019-05-29'
# Optional: Set API endpoint
# omise.api_url = 'https://api.omise.co'
Django Configurationโ
Add to your Django settings:
# settings.py
import os
OMISE_SECRET_KEY = os.environ.get('OMISE_SECRET_KEY')
OMISE_PUBLIC_KEY = os.environ.get('OMISE_PUBLIC_KEY')
OMISE_API_VERSION = '2019-05-29'
# Initialize in apps.py or __init__.py
import omise
omise.api_secret = OMISE_SECRET_KEY
omise.api_version = OMISE_API_VERSION
Flask Configurationโ
# config.py
import os
class Config:
OMISE_SECRET_KEY = os.environ.get('OMISE_SECRET_KEY')
OMISE_PUBLIC_KEY = os.environ.get('OMISE_PUBLIC_KEY')
OMISE_API_VERSION = '2019-05-29'
# app.py
from flask import Flask
import omise
app = Flask(__name__)
app.config.from_object('config.Config')
omise.api_secret = app.config['OMISE_SECRET_KEY']
omise.api_version = app.config['OMISE_API_VERSION']
Environment Variablesโ
Add to your .env file:
# Development/Test
OMISE_SECRET_KEY=skey_test_123456789
OMISE_PUBLIC_KEY=pkey_test_123456789
# Production
# OMISE_SECRET_KEY=skey_live_123456789
# OMISE_PUBLIC_KEY=pkey_live_123456789
Authenticationโ
The library uses your secret key for all API operations:
import omise
# Option 1: Global configuration (recommended)
omise.api_secret = os.environ['OMISE_SECRET_KEY']
# Option 2: Per-request configuration
charge = omise.Charge.retrieve('chrg_test_123', key='skey_test_alternate')
Common Operationsโ
Creating a Chargeโ
With a Tokenโ
import omise
# Create a charge with a card token
charge = omise.Charge.create(
amount=100000, # 1,000.00 THB (in smallest currency unit)
currency='THB',
card='tokn_test_123',
description='Order #1234',
metadata={
'order_id': '1234',
'customer_name': 'John Doe'
}
)
if charge.paid:
print(f"Charge successful: {charge.id}")
else:
print(f"Charge failed: {charge.failure_message}")
With Type Hintsโ
from typing import Dict, Any
import omise
def create_charge(
amount: int,
currency: str,
token: str,
metadata: Dict[str, Any]
) -> omise.Charge:
"""Create a charge with the given parameters."""
charge = omise.Charge.create(
amount=amount,
currency=currency,
card=token,
metadata=metadata
)
return charge
# Usage
charge = create_charge(
amount=100000,
currency='THB',
token='tokn_test_123',
metadata={'order_id': '1234'}
)
With a Customerโ
# Create a charge for an existing customer
charge = omise.Charge.create(
amount=50000,
currency='THB',
customer='cust_test_123',
description='Subscription payment'
)
With 3D Secureโ
# Create a charge that may require 3D Secure
charge = omise.Charge.create(
amount=100000,
currency='THB',
card='tokn_test_123',
return_uri='https://example.com/payment/callback'
)
if charge.authorized:
if charge.authorize_uri:
# Redirect customer to authorize_uri for 3D Secure
return redirect(charge.authorize_uri)
else:
# Charge completed without 3D Secure
process_successful_payment(charge)
Retrieving a Chargeโ
# Retrieve a charge by ID
charge = omise.Charge.retrieve('chrg_test_123')
print(f"Amount: {charge.amount}")
print(f"Currency: {charge.currency}")
print(f"Status: {charge.status}")
print(f"Paid: {charge.paid}")
Listing Chargesโ
from datetime import datetime, timedelta
# List all charges with pagination
charges = omise.Charge.list(
limit=20,
offset=0,
order='reverse_chronological'
)
for charge in charges:
print(f"{charge.id}: {charge.amount} {charge.currency}")
# List charges with filters
week_ago = (datetime.now() - timedelta(days=7)).date().isoformat()
today = datetime.now().date().isoformat()
recent_charges = omise.Charge.list(
from_date=week_ago,
to_date=today
)
Creating a Customerโ
# Create a customer without a card
customer = omise.Customer.create(
email='customer@example.com',
description='John Doe',
metadata={
'user_id': '12345',
'account_type': 'premium'
}
)
print(f"Customer created: {customer.id}")
Saving a Card to a Customerโ
# Update customer with a card token
customer = omise.Customer.retrieve('cust_test_123')
customer.update(card='tokn_test_456')
print(f"Card saved: {customer.default_card}")
# Or create customer with card in one step
customer = omise.Customer.create(
email='customer@example.com',
description='John Doe',
card='tokn_test_123'
)
Listing Customer Cardsโ
customer = omise.Customer.retrieve('cust_test_123')
for card in customer.cards:
print(f"{card.brand} ending in {card.last_digits}")
print(f"Expires: {card.expiration_month}/{card.expiration_year}")
Creating a Refundโ
# Full refund
refund = omise.Refund.create(
charge='chrg_test_123',
amount=None # None for full refund
)
# Partial refund
refund = omise.Refund.create(
charge='chrg_test_123',
amount=25000, # 250.00 THB
metadata={
'reason': 'customer_request',
'ticket_id': 'TICKET-123'
}
)
print(f"Refund {refund.id}: {refund.amount} {refund.currency}")
Creating a Transferโ
# Create a transfer to your bank account
transfer = omise.Transfer.create(
amount=500000, # 5,000.00 THB
recipient='recp_test_123',
metadata={
'payout_id': 'PAYOUT-456'
}
)
print(f"Transfer {transfer.id}: {transfer.amount}")
Alternative Payment Methodsโ
Creating a Sourceโ
# Prompt Pay QR
source = omise.Source.create(
type='promptpay',
amount=100000,
currency='THB'
)
# Display QR code to customer
print(f"QR Code URL: {source.scannable_code.image.download_uri}")
# Create charge with source
charge = omise.Charge.create(
amount=100000,
currency='THB',
source=source.id,
return_uri='https://example.com/payment/callback'
)
Internet Bankingโ
# Internet Banking
source = omise.Source.create(
type='internet_banking_scb',
amount=100000,
currency='THB'
)
charge = omise.Charge.create(
amount=100000,
currency='THB',
source=source.id,
return_uri='https://example.com/payment/callback'
)
# Redirect customer to authorize_uri
return redirect(charge.authorize_uri)
Mobile Bankingโ
# Mobile Banking (SCB Easy)
source = omise.Source.create(
type='mobile_banking_scb',
amount=100000,
currency='THB'
)
charge = omise.Charge.create(
amount=100000,
currency='THB',
source=source.id,
return_uri='https://example.com/payment/callback'
)
Installmentsโ
# Installment payment
source = omise.Source.create(
type='installment_kbank',
amount=100000,
currency='THB',
installment_term=6 # 6 months
)
charge = omise.Charge.create(
amount=100000,
currency='THB',
source=source.id,
return_uri='https://example.com/payment/callback'
)
Error Handlingโ
The library raises specific exception types for different errors:
import omise
from omise.errors import (
OmiseError,
AuthenticationError,
InvalidRequestError,
CardError,
APIError,
ConnectionError
)
try:
charge = omise.Charge.create(
amount=100000,
currency='THB',
card='tokn_test_123'
)
except AuthenticationError as e:
# Invalid API key
print(f"Authentication failed: {e}")
except InvalidRequestError as e:
# Invalid parameters
print(f"Invalid request: {e}")
print(f"Status code: {e.http_status}")
except CardError as e:
# Card declined
print(f"Card error: {e}")
print(f"Failure code: {e.code}")
except APIError as e:
# General API error
print(f"API error: {e}")
except ConnectionError as e:
# Network error
print(f"Connection failed: {e}")
except OmiseError as e:
# Catch-all for any Omise error
print(f"Omise error: {e}")
Handling Specific Card Errorsโ
from omise.errors import CardError
try:
charge = omise.Charge.create(amount=100000, currency='THB', card=token)
except CardError as e:
error_messages = {
'insufficient_fund': 'Insufficient funds on card',
'stolen_or_lost_card': 'Card reported as stolen or lost',
'invalid_security_code': 'Invalid CVV code',
'payment_cancelled': 'Payment was cancelled'
}
message = error_messages.get(e.code, f"Card error: {e}")
return {'error': message}, 400
Django Integrationโ
Views Exampleโ
# views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.views.decorators.http import require_http_methods
import omise
from omise.errors import OmiseError
from .models import Order, Payment
@require_http_methods(["POST"])
def create_payment(request, order_id):
"""Create a payment charge for an order."""
order = get_object_or_404(Order, id=order_id)
token = request.POST.get('omise_token')
try:
charge = omise.Charge.create(
amount=int(order.total * 100), # Convert to smallest unit
currency='THB',
card=token,
description=f"Order #{order.id}",
metadata={
'order_id': str(order.id),
'customer_email': order.email
},
return_uri=request.build_absolute_uri(
reverse('payment_callback')
)
)
# Save payment record
payment = Payment.objects.create(
order=order,
charge_id=charge.id,
amount=order.total,
status=charge.status,
paid=charge.paid
)
if charge.paid:
order.payment_status = 'paid'
order.save()
messages.success(request, 'Payment successful!')
return redirect('order_detail', order_id=order.id)
elif charge.authorize_uri:
# 3D Secure required
return redirect(charge.authorize_uri)
else:
messages.error(request, charge.failure_message)
return redirect('payment_form', order_id=order.id)
except OmiseError as e:
logger.error(f"Omise error: {e}")
messages.error(request, 'Payment failed. Please try again.')
return redirect('payment_form', order_id=order.id)
@require_http_methods(["GET"])
def payment_callback(request):
"""Handle payment callback after 3D Secure."""
charge_id = request.GET.get('id')
try:
charge = omise.Charge.retrieve(charge_id)
payment = Payment.objects.get(charge_id=charge.id)
payment.status = charge.status
payment.paid = charge.paid
payment.save()
if charge.paid:
payment.order.payment_status = 'paid'
payment.order.save()
messages.success(request, 'Payment successful!')
return redirect('order_detail', order_id=payment.order.id)
else:
messages.error(request, charge.failure_message)
return redirect('payment_form', order_id=payment.order.id)
except omise.OmiseError as e:
messages.error(request, 'Payment verification failed.')
return redirect('home')
Models Integrationโ
# models.py
from django.db import models
from django.core.exceptions import ValidationError
import omise
from omise.errors import OmiseError
class Payment(models.Model):
"""Payment model for tracking Omise charges."""
STATUS_CHOICES = [
('pending', 'Pending'),
('successful', 'Successful'),
('failed', 'Failed'),
('refunded', 'Refunded'),
]
order = models.ForeignKey('Order', on_delete=models.CASCADE)
charge_id = models.CharField(max_length=100, unique=True, null=True)
amount = models.DecimalField(max_digits=10, decimal_places=2)
currency = models.CharField(max_length=3, default='THB')
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
paid = models.BooleanField(default=False)
failure_code = models.CharField(max_length=100, blank=True)
failure_message = models.TextField(blank=True)
refund_id = models.CharField(max_length=100, blank=True)
refund_amount = models.DecimalField(max_digits=10, decimal_places=2, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
def charge(self, token: str) -> omise.Charge:
"""Create a charge with the given token."""
charge = omise.Charge.create(
amount=int(self.amount * 100),
currency=self.currency,
card=token,
description=f"Order #{self.order.id}",
metadata=self.get_charge_metadata()
)
self.charge_id = charge.id
self.status = charge.status
self.paid = charge.paid
self.save()
return charge
def refund(self, refund_amount: float = None) -> omise.Refund:
"""Create a refund for this payment."""
if not self.charge_id:
raise ValueError("Cannot refund payment without charge_id")
refund = omise.Refund.create(
charge=self.charge_id,
amount=int(refund_amount * 100) if refund_amount else None
)
self.refund_id = refund.id
self.refund_amount = refund.amount / 100
self.status = 'refunded'
self.save()
return refund
def refresh_status(self) -> None:
"""Refresh the payment status from Omise."""
if not self.charge_id:
return
charge = omise.Charge.retrieve(self.charge_id)
self.status = charge.status
self.paid = charge.paid
self.failure_code = charge.failure_code or ''
self.failure_message = charge.failure_message or ''
self.save()
def get_charge_metadata(self) -> dict:
"""Get metadata for the charge."""
return {
'order_id': str(self.order.id),
'customer_email': self.order.email,
'customer_name': self.order.customer_name
}
Celery Task for Async Processingโ
# tasks.py
from celery import shared_task
from django.core.mail import send_mail
import omise
from omise.errors import OmiseError, CardError
from .models import Payment
@shared_task(bind=True, max_retries=3)
def process_charge(self, payment_id: int, token: str):
"""Process a charge asynchronously."""
try:
payment = Payment.objects.get(id=payment_id)
charge = payment.charge(token)
if charge.paid:
# Send success email
send_mail(
'Payment Confirmed',
f'Your payment of {payment.amount} {payment.currency} was successful.',
'noreply@example.com',
[payment.order.email],
fail_silently=False,
)
else:
# Send failure email
send_mail(
'Payment Failed',
f'Your payment failed: {charge.failure_message}',
'noreply@example.com',
[payment.order.email],
fail_silently=False,
)
except CardError as e:
payment.status = 'failed'
payment.failure_message = str(e)
payment.save()
except OmiseError as e:
# Retry on network errors
raise self.retry(exc=e, countdown=60)
@shared_task
def refresh_payment_statuses():
"""Refresh pending payment statuses."""
pending_payments = Payment.objects.filter(status='pending')
for payment in pending_payments:
try:
payment.refresh_status()
except OmiseError:
continue
Flask Integrationโ
Application Setupโ
# app.py
from flask import Flask, request, render_template, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
import omise
from omise.errors import OmiseError, CardError
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://localhost/myapp'
db = SQLAlchemy(app)
# Configure Omise
omise.api_secret = os.environ.get('OMISE_SECRET_KEY')
omise.api_version = '2019-05-29'
@app.route('/payment/<int:order_id>', methods=['GET'])
def payment_form(order_id):
"""Display payment form."""
order = Order.query.get_or_404(order_id)
return render_template('payment.html', order=order)
@app.route('/payment/<int:order_id>', methods=['POST'])
def create_payment(order_id):
"""Process payment."""
order = Order.query.get_or_404(order_id)
token = request.form.get('omise_token')
try:
charge = omise.Charge.create(
amount=int(order.total * 100),
currency='THB',
card=token,
description=f"Order #{order.id}",
metadata={
'order_id': str(order.id),
'customer_email': order.email
},
return_uri=url_for('payment_callback', _external=True)
)
# Save payment
payment = Payment(
order_id=order.id,
charge_id=charge.id,
amount=order.total,
status=charge.status,
paid=charge.paid
)
db.session.add(payment)
db.session.commit()
if charge.paid:
flash('Payment successful!', 'success')
return redirect(url_for('order_detail', order_id=order.id))
elif charge.authorize_uri:
return redirect(charge.authorize_uri)
else:
flash(charge.failure_message, 'error')
return redirect(url_for('payment_form', order_id=order.id))
except CardError as e:
flash(f'Card error: {e}', 'error')
return redirect(url_for('payment_form', order_id=order.id))
except OmiseError as e:
app.logger.error(f"Omise error: {e}")
flash('Payment failed. Please try again.', 'error')
return redirect(url_for('payment_form', order_id=order.id))
@app.route('/payment/callback')
def payment_callback():
"""Handle payment callback."""
charge_id = request.args.get('id')
try:
charge = omise.Charge.retrieve(charge_id)
payment = Payment.query.filter_by(charge_id=charge.id).first_or_404()
payment.status = charge.status
payment.paid = charge.paid
db.session.commit()
if charge.paid:
flash('Payment successful!', 'success')
return redirect(url_for('order_detail', order_id=payment.order_id))
else:
flash(charge.failure_message, 'error')
return redirect(url_for('payment_form', order_id=payment.order_id))
except OmiseError as e:
flash('Payment verification failed.', 'error')
return redirect(url_for('home'))
Async Supportโ
Using asyncioโ
import asyncio
import aiohttp
import omise
async def create_charge_async(amount: int, currency: str, token: str):
"""Create a charge asynchronously."""
# Note: omise-python doesn't have native async support
# Use run_in_executor for concurrent operations
loop = asyncio.get_event_loop()
charge = await loop.run_in_executor(
None,
omise.Charge.create,
amount,
currency,
token
)
return charge
async def process_multiple_charges(charges_data):
"""Process multiple charges concurrently."""
tasks = [
create_charge_async(
data['amount'],
data['currency'],
data['token']
)
for data in charges_data
]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
# Usage
async def main():
charges_data = [
{'amount': 100000, 'currency': 'THB', 'token': 'tokn_test_1'},
{'amount': 200000, 'currency': 'THB', 'token': 'tokn_test_2'},
]
results = await process_multiple_charges(charges_data)
asyncio.run(main())
Best Practicesโ
1. Use Environment Variables for Keysโ
import os
from dotenv import load_dotenv
load_dotenv()
# Raise error if key is missing
OMISE_SECRET_KEY = os.environ['OMISE_SECRET_KEY']
if not OMISE_SECRET_KEY:
raise ValueError("OMISE_SECRET_KEY environment variable is not set")
omise.api_secret = OMISE_SECRET_KEY
2. Handle Idempotencyโ
import uuid
import omise
def create_idempotent_charge(amount: int, currency: str, token: str, order_id: str):
"""Create a charge with idempotency key."""
idempotency_key = f"order-{order_id}-{uuid.uuid4()}"
charge = omise.Charge.create(
amount=amount,
currency=currency,
card=token,
headers={'Idempotency-Key': idempotency_key}
)
return charge
3. Use Connection Poolingโ
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
# Configure retry strategy
retry_strategy = Retry(
total=3,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE", "POST"],
backoff_factor=1
)
adapter = HTTPAdapter(max_retries=retry_strategy, pool_connections=10, pool_maxsize=10)
session = requests.Session()
session.mount("https://", adapter)
# Use custom session with omise
omise.api_session = session
4. Store Minimal Dataโ
class Payment:
"""Store only IDs, not full objects."""
def __init__(self, charge_id: str):
self.charge_id = charge_id
self._charge = None
@property
def charge(self):
"""Lazy load charge when needed."""
if self._charge is None and self.charge_id:
self._charge = omise.Charge.retrieve(self.charge_id)
return self._charge
def refresh(self):
"""Refresh charge data."""
self._charge = None
return self.charge
5. Validate Before API Callsโ
from decimal import Decimal
def validate_charge_params(amount: Decimal, currency: str):
"""Validate charge parameters before API call."""
if amount < Decimal('20.00'):
raise ValueError("Amount must be at least 20 THB")
if currency not in ['THB', 'USD', 'SGD', 'JPY']:
raise ValueError(f"Currency {currency} not supported")
return True
def create_validated_charge(amount: Decimal, currency: str, token: str):
"""Create a charge with validation."""
validate_charge_params(amount, currency)
charge = omise.Charge.create(
amount=int(amount * 100),
currency=currency,
card=token
)
return charge
6. Implement Loggingโ
import logging
import omise
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def create_charge_with_logging(amount: int, currency: str, token: str):
"""Create a charge with comprehensive logging."""
logger.info(f"Creating charge: amount={amount}, currency={currency}")
try:
charge = omise.Charge.create(
amount=amount,
currency=currency,
card=token
)
logger.info(f"Charge created: {charge.id}, paid={charge.paid}")
return charge
except omise.OmiseError as e:
logger.error(f"Charge failed: {e}", exc_info=True)
raise
Testingโ
Pytest Examplesโ
# test_payments.py
import pytest
import omise
from unittest.mock import patch, Mock
@pytest.fixture
def mock_charge():
"""Mock charge object."""
charge = Mock()
charge.id = 'chrg_test_123'
charge.amount = 100000
charge.currency = 'THB'
charge.paid = True
charge.status = 'successful'
return charge
def test_create_charge(mock_charge):
"""Test charge creation."""
with patch('omise.Charge.create', return_value=mock_charge):
charge = omise.Charge.create(
amount=100000,
currency='THB',
card='tokn_test_123'
)
assert charge.id == 'chrg_test_123'
assert charge.paid is True
def test_create_charge_with_error():
"""Test charge creation with error."""
with patch('omise.Charge.create', side_effect=omise.CardError('insufficient_fund', 'Insufficient funds')):
with pytest.raises(omise.CardError) as exc_info:
omise.Charge.create(
amount=100000,
currency='THB',
card='tokn_test_123'
)
assert 'insufficient_fund' in str(exc_info.value)
@pytest.fixture
def payment_service():
"""Payment service fixture."""
return PaymentService()
def test_process_payment(payment_service, mock_charge):
"""Test payment processing."""
with patch('omise.Charge.create', return_value=mock_charge):
result = payment_service.process_payment(
amount=1000.00,
token='tokn_test_123'
)
assert result['success'] is True
assert result['charge_id'] == 'chrg_test_123'
Using VCR.py for API Testingโ
# test_integration.py
import pytest
import vcr
import omise
# Configure VCR
my_vcr = vcr.VCR(
cassette_library_dir='tests/fixtures/vcr_cassettes',
record_mode='once',
filter_headers=['authorization'],
)
@my_vcr.use_cassette('create_charge.yaml')
def test_create_charge_integration():
"""Integration test with VCR."""
charge = omise.Charge.create(
amount=100000,
currency='THB',
card='tokn_test_123'
)
assert charge.paid is True
assert charge.amount == 100000
Mock Decorator Patternโ
from unittest.mock import patch
from functools import wraps
def mock_omise(func):
"""Decorator to mock Omise API calls."""
@wraps(func)
def wrapper(*args, **kwargs):
with patch('omise.Charge.create') as mock_create:
mock_charge = Mock()
mock_charge.id = 'chrg_test_123'
mock_charge.paid = True
mock_create.return_value = mock_charge
return func(*args, **kwargs)
return wrapper
@mock_omise
def test_payment_flow():
"""Test payment flow with decorator."""
charge = omise.Charge.create(amount=100000, currency='THB', card='tokn_test_123')
assert charge.paid is True
Troubleshootingโ
SSL Certificate Errorsโ
import certifi
import omise
# Use certifi for SSL verification
omise.ca_bundle = certifi.where()
# Or disable SSL verification (not recommended for production)
# omise.verify_ssl = False
Connection Timeoutsโ
import omise
# Set custom timeout (in seconds)
omise.timeout = 60
# Or per-request
charge = omise.Charge.create(
amount=100000,
currency='THB',
card='tokn_test_123',
timeout=60
)
Debug Modeโ
import logging
import omise
# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
omise.debug = True
# This will output:
# - Request URL and method
# - Request headers
# - Request body
# - Response status
# - Response body
Webhook Signature Verificationโ
import hmac
import hashlib
def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
"""Verify webhook signature."""
expected_signature = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
# Flask example
@app.route('/webhooks/omise', methods=['POST'])
def omise_webhook():
"""Handle Omise webhook."""
payload = request.get_data()
signature = request.headers.get('Omise-Signature')
if not verify_webhook_signature(payload, signature, os.environ['OMISE_WEBHOOK_SECRET']):
return {'error': 'Invalid signature'}, 401
event = request.get_json()
# Process event
return {'status': 'ok'}
FAQโ
How do I handle webhooks in Django?โ
# views.py
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse
import json
import hmac
import hashlib
@csrf_exempt
def omise_webhook(request):
"""Handle Omise webhook."""
if request.method != 'POST':
return JsonResponse({'error': 'Method not allowed'}, status=405)
# Verify signature
payload = request.body
signature = request.headers.get('Omise-Signature')
expected_signature = hmac.new(
settings.OMISE_WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected_signature):
return JsonResponse({'error': 'Invalid signature'}, status=401)
# Process event
event = json.loads(payload)
if event['key'] == 'charge.complete':
handle_charge_complete(event['data'])
elif event['key'] == 'refund.create':
handle_refund_create(event['data'])
return JsonResponse({'status': 'ok'})
def handle_charge_complete(charge_data):
"""Handle charge completion."""
charge = omise.Charge.retrieve(charge_data['id'])
payment = Payment.objects.get(charge_id=charge.id)
payment.status = charge.status
payment.paid = charge.paid
payment.save()
How do I test payments without real charges?โ
import os
import omise
# Use test API keys
omise.api_secret = 'skey_test_123456789'
# Use test card tokens
# Successful: tokn_test_5086xl7ddjbases4sq3i
# Declined: tokn_test_no1
# Create test charge
charge = omise.Charge.create(
amount=100000,
currency='THB',
card='tokn_test_5086xl7ddjbases4sq3i'
)
# Check test mode
print(f"Test mode: {not charge.livemode}")
How do I handle currency conversion?โ
from decimal import Decimal
class Payment:
"""Payment with currency conversion."""
def __init__(self, amount_baht: Decimal):
self.amount_baht = amount_baht
@property
def amount_satang(self) -> int:
"""Convert baht to satang."""
return int(self.amount_baht * 100)
@staticmethod
def from_satang(amount_satang: int) -> 'Payment':
"""Create payment from satang."""
amount_baht = Decimal(amount_satang) / 100
return Payment(amount_baht)
# Usage
payment = Payment(Decimal('1000.00')) # 1000.00 THB
charge = omise.Charge.create(
amount=payment.amount_satang, # 100000 satang
currency='THB',
card='tokn_test_123'
)
How do I implement retry logic?โ
import time
from typing import Optional
import omise
from omise.errors import ConnectionError
def create_charge_with_retry(
amount: int,
currency: str,
token: str,
max_retries: int = 3
) -> Optional[omise.Charge]:
"""Create charge with exponential backoff retry."""
for attempt in range(max_retries):
try:
charge = omise.Charge.create(
amount=amount,
currency=currency,
card=token
)
return charge
except ConnectionError as e:
if attempt < max_retries - 1:
wait_time = 2 ** attempt # 1, 2, 4 seconds
time.sleep(wait_time)
else:
raise
# Or use tenacity library
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)
)
def create_charge_tenacity(amount: int, currency: str, token: str):
"""Create charge with tenacity retry."""
return omise.Charge.create(amount=amount, currency=currency, card=token)
How do I handle partial refunds?โ
import omise
def refund_charge(charge_id: str, refund_amount: float = None):
"""Refund a charge (partial or full)."""
# Get current charge
charge = omise.Charge.retrieve(charge_id)
# Calculate refundable amount
refundable = charge.amount - charge.refunded
if refund_amount:
refund_amount_satang = int(refund_amount * 100)
if refund_amount_satang > refundable:
raise ValueError(f"Refund amount exceeds refundable amount: {refundable / 100}")
else:
refund_amount_satang = None # Full refund
# Create refund
refund = omise.Refund.create(
charge=charge_id,
amount=refund_amount_satang
)
return refund
# Usage
refund = refund_charge('chrg_test_123', refund_amount=250.00)
How do I save multiple cards for a customer?โ
import omise
def add_card_to_customer(customer_id: str, token: str) -> omise.Card:
"""Add a card to a customer."""
customer = omise.Customer.retrieve(customer_id)
customer.update(card=token)
return customer.default_card
def list_customer_cards(customer_id: str):
"""List all cards for a customer."""
customer = omise.Customer.retrieve(customer_id)
return list(customer.cards)
def charge_specific_card(customer_id: str, card_id: str, amount: int):
"""Charge a specific card."""
charge = omise.Charge.create(
amount=amount,
currency='THB',
customer=customer_id,
card=card_id # Specific card ID
)
return charge
How do I implement subscription billing?โ
from datetime import datetime, timedelta
import omise
class SubscriptionManager:
"""Manage subscription billing."""
def __init__(self, customer_id: str, plan_amount: int):
self.customer_id = customer_id
self.plan_amount = plan_amount
def charge_monthly(self) -> omise.Charge:
"""Charge monthly subscription."""
charge = omise.Charge.create(
amount=self.plan_amount,
currency='THB',
customer=self.customer_id,
description=f"Subscription {datetime.now().strftime('%B %Y')}"
)
return charge
def handle_failed_payment(self, charge: omise.Charge):
"""Handle failed subscription payment."""
# Send notification
# Update subscription status
# Retry after grace period
pass
# Usage with Celery
from celery import shared_task
@shared_task
def charge_subscriptions():
"""Charge all active subscriptions."""
from myapp.models import Subscription
subscriptions = Subscription.objects.filter(status='active')
for subscription in subscriptions:
manager = SubscriptionManager(
subscription.customer_id,
subscription.plan_amount
)
try:
charge = manager.charge_monthly()
subscription.last_charge_date = datetime.now()
subscription.save()
except omise.CardError as e:
manager.handle_failed_payment(charge)
Related Resourcesโ
- Omise API Documentation
- omise-python GitHub Repository
- Python API Reference
- Omise Dashboard
- Webhooks Guide
- Testing Guide
Next Stepsโ
Supportโ
If you encounter issues with the Python library:
- Check the GitHub Issues
- Review the API documentation
- Contact support@omise.co with:
- Python version
- omise-python library version
- Error message and traceback
- Steps to reproduce