Skip to main content

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โ€‹

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)

Next Stepsโ€‹

Supportโ€‹

If you encounter issues with the Python library:

  1. Check the GitHub Issues
  2. Review the API documentation
  3. Contact support@omise.co with:
    • Python version
    • omise-python library version
    • Error message and traceback
    • Steps to reproduce