Ruby Library (omise-ruby)
The omise-ruby gem provides a comprehensive Ruby interface to the Omise API with excellent Rails integration, idiomatic Ruby code, and robust error handling.
Installationโ
Using Bundler (Recommended)โ
Add to your Gemfile:
gem 'omise', '~> 0.11.0'
Then run:
bundle install
Using RubyGemsโ
gem install omise
Requirementsโ
- Ruby 2.6 or higher (including Ruby 3.x)
- Bundler (for Rails applications)
- Active Internet connection for API calls
Quick Startโ
Basic Configurationโ
require 'omise'
# Configure with your API keys
Omise.api_key = 'skey_test_123456789'
Omise.api_version = '2019-05-29'
# Optional: Set API endpoint (for testing)
# Omise.api_url = 'https://api.omise.co'
Rails Configurationโ
Create an initializer at config/initializers/omise.rb:
# config/initializers/omise.rb
Omise.api_key = ENV['OMISE_SECRET_KEY']
Omise.api_version = '2019-05-29'
# Configure timeout (optional)
Omise.timeout = 30 # seconds
# Enable debug mode in development
Omise.debug = Rails.env.development?
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:
# Option 1: Global configuration (recommended)
Omise.api_key = ENV['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โ
# Create a charge with a card token
charge = Omise::Charge.create(
amount: 100_000, # 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
puts "Charge successful: #{charge.id}"
else
puts "Charge failed: #{charge.failure_message}"
end
With a Customerโ
# Create a charge for an existing customer
charge = Omise::Charge.create(
amount: 50_000,
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: 100_000,
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
redirect_to charge.authorize_uri
else
# Charge completed without 3D Secure
process_successful_payment(charge)
end
end
Retrieving a Chargeโ
# Retrieve a charge by ID
charge = Omise::Charge.retrieve('chrg_test_123')
puts "Amount: #{charge.amount}"
puts "Currency: #{charge.currency}"
puts "Status: #{charge.status}"
puts "Paid: #{charge.paid}"
Listing Chargesโ
# List all charges with pagination
charges = Omise::Charge.list(
limit: 20,
offset: 0,
order: 'reverse_chronological'
)
charges.each do |charge|
puts "#{charge.id}: #{charge.amount} #{charge.currency}"
end
# List charges with filters
recent_charges = Omise::Charge.list(
from: 1.week.ago.to_date.iso8601,
to: Date.today.iso8601
)
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'
}
)
puts "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')
puts "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')
customer.cards.each do |card|
puts "#{card.brand} ending in #{card.last_digits}"
puts "Expires: #{card.expiration_month}/#{card.expiration_year}"
end
Creating a Refundโ
# Full refund
refund = Omise::Refund.create(
charge: 'chrg_test_123',
amount: nil # nil for full refund
)
# Partial refund
refund = Omise::Refund.create(
charge: 'chrg_test_123',
amount: 25_000, # 250.00 THB
metadata: {
reason: 'customer_request',
ticket_id: 'TICKET-123'
}
)
puts "Refund #{refund.id}: #{refund.amount} #{refund.currency}"
Creating a Transferโ
# Create a transfer to your bank account
transfer = Omise::Transfer.create(
amount: 500_000, # 5,000.00 THB
recipient: 'recp_test_123',
metadata: {
payout_id: 'PAYOUT-456'
}
)
puts "Transfer #{transfer.id}: #{transfer.amount}"
Alternative Payment Methodsโ
Creating a Sourceโ
# Prompt Pay QR
source = Omise::Source.create(
type: 'promptpay',
amount: 100_000,
currency: 'THB'
)
# Display QR code to customer
puts "QR Code URL: #{source.scannable_code.image.download_uri}"
# Create charge with source
charge = Omise::Charge.create(
amount: 100_000,
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: 100_000,
currency: 'THB'
)
charge = Omise::Charge.create(
amount: 100_000,
currency: 'THB',
source: source.id,
return_uri: 'https://example.com/payment/callback'
)
# Redirect customer to authorize_uri
redirect_to charge.authorize_uri
Mobile Bankingโ
# Mobile Banking (SCB Easy)
source = Omise::Source.create(
type: 'mobile_banking_scb',
amount: 100_000,
currency: 'THB'
)
charge = Omise::Charge.create(
amount: 100_000,
currency: 'THB',
source: source.id,
return_uri: 'https://example.com/payment/callback'
)
Installmentsโ
# Installment payment
source = Omise::Source.create(
type: 'installment_kbank',
amount: 100_000,
currency: 'THB',
installment_term: 6 # 6 months
)
charge = Omise::Charge.create(
amount: 100_000,
currency: 'THB',
source: source.id,
return_uri: 'https://example.com/payment/callback'
)
Error Handlingโ
The library raises specific exception types for different errors:
begin
charge = Omise::Charge.create(
amount: 100_000,
currency: 'THB',
card: 'tokn_test_123'
)
rescue Omise::AuthenticationError => e
# Invalid API key
puts "Authentication failed: #{e.message}"
rescue Omise::InvalidRequestError => e
# Invalid parameters
puts "Invalid request: #{e.message}"
puts "Status code: #{e.http_status}"
rescue Omise::CardError => e
# Card declined
puts "Card error: #{e.message}"
puts "Failure code: #{e.code}"
rescue Omise::APIError => e
# General API error
puts "API error: #{e.message}"
rescue Omise::ConnectionError => e
# Network error
puts "Connection failed: #{e.message}"
rescue Omise::Error => e
# Catch-all for any Omise error
puts "Omise error: #{e.message}"
end
Handling Specific Card Errorsโ
begin
charge = Omise::Charge.create(amount: 100_000, currency: 'THB', card: token)
rescue Omise::CardError => e
case e.code
when 'insufficient_fund'
flash[:error] = 'Insufficient funds on card'
when 'stolen_or_lost_card'
flash[:error] = 'Card reported as stolen or lost'
when 'invalid_security_code'
flash[:error] = 'Invalid CVV code'
when 'payment_cancelled'
flash[:error] = 'Payment was cancelled'
else
flash[:error] = "Card error: #{e.message}"
end
redirect_to payment_path
end
Rails Integrationโ
Controller Exampleโ
# app/controllers/payments_controller.rb
class PaymentsController < ApplicationController
def create
@order = Order.find(params[:order_id])
charge = Omise::Charge.create(
amount: (@order.total * 100).to_i, # Convert to smallest unit
currency: 'THB',
card: params[:omise_token],
description: "Order ##{@order.id}",
metadata: {
order_id: @order.id,
customer_email: @order.email
},
return_uri: payment_callback_url
)
if charge.paid
@order.update!(
payment_status: 'paid',
charge_id: charge.id
)
redirect_to order_path(@order), notice: 'Payment successful!'
elsif charge.authorize_uri
# 3D Secure required
redirect_to charge.authorize_uri
else
flash[:error] = charge.failure_message
render :new
end
rescue Omise::Error => e
Rails.logger.error "Omise error: #{e.message}"
flash[:error] = 'Payment failed. Please try again.'
render :new
end
def callback
charge = Omise::Charge.retrieve(params[:id])
@order = Order.find_by(charge_id: charge.id)
if charge.paid
@order.update!(payment_status: 'paid')
redirect_to order_path(@order), notice: 'Payment successful!'
else
flash[:error] = charge.failure_message
redirect_to new_payment_path(order_id: @order.id)
end
end
end
Model Integrationโ
# app/models/payment.rb
class Payment < ApplicationRecord
belongs_to :order
validates :amount, presence: true, numericality: { greater_than: 0 }
validates :currency, presence: true
# Create a charge
def charge!(token)
charge = Omise::Charge.create(
amount: (amount * 100).to_i,
currency: currency,
card: token,
description: "Order ##{order.id}",
metadata: charge_metadata
)
update!(
charge_id: charge.id,
status: charge.status,
paid: charge.paid
)
charge
end
# Refund a charge
def refund!(refund_amount = nil)
refund = Omise::Refund.create(
charge: charge_id,
amount: refund_amount ? (refund_amount * 100).to_i : nil
)
update!(
refund_id: refund.id,
refund_amount: refund.amount / 100.0,
status: 'refunded'
)
refund
end
# Refresh charge status
def refresh_status!
charge = Omise::Charge.retrieve(charge_id)
update!(
status: charge.status,
paid: charge.paid,
failure_code: charge.failure_code,
failure_message: charge.failure_message
)
end
private
def charge_metadata
{
order_id: order.id,
customer_email: order.email,
customer_name: order.customer_name
}
end
end
Background Job for Charge Processingโ
# app/jobs/process_charge_job.rb
class ProcessChargeJob < ApplicationJob
queue_as :payments
retry_on Omise::ConnectionError, wait: :exponentially_longer, attempts: 5
discard_on Omise::AuthenticationError
def perform(payment_id, token)
payment = Payment.find(payment_id)
charge = payment.charge!(token)
if charge.paid
OrderMailer.payment_confirmed(payment.order).deliver_later
else
OrderMailer.payment_failed(payment.order, charge.failure_message).deliver_later
end
rescue Omise::CardError => e
payment.update!(
status: 'failed',
failure_message: e.message
)
OrderMailer.payment_failed(payment.order, e.message).deliver_later
end
end
Best Practicesโ
1. Use Environment Variables for Keysโ
# config/initializers/omise.rb
Omise.api_key = ENV.fetch('OMISE_SECRET_KEY') do
raise 'OMISE_SECRET_KEY environment variable is not set'
end
# Never commit keys to version control
# Add to .gitignore:
# .env
# .env.local
2. Handle Idempotencyโ
# Use idempotency key for charge creation
charge = Omise::Charge.create(
amount: 100_000,
currency: 'THB',
card: token,
headers: {
'Idempotency-Key' => "order-#{order.id}-#{Time.now.to_i}"
}
)
3. Use Background Jobs for API Callsโ
# Process charges asynchronously
class ChargeCustomerJob < ApplicationJob
queue_as :payments
def perform(customer_id, amount)
customer = Customer.find(customer_id)
charge = Omise::Charge.create(
amount: (amount * 100).to_i,
currency: 'THB',
customer: customer.omise_customer_id
)
# Process result
rescue Omise::Error => e
# Handle error
end
end
4. Store Minimal Dataโ
# Store only IDs, not full objects
class Order < ApplicationRecord
# Good: Store only the ID
def create_charge(token)
charge = Omise::Charge.create(...)
update!(charge_id: charge.id)
end
# Good: Retrieve when needed
def charge
@charge ||= Omise::Charge.retrieve(charge_id) if charge_id
end
end
5. Validate Before API Callsโ
class Payment < ApplicationRecord
validates :amount, numericality: { greater_than: 2000 } # Minimum 20 THB
validates :currency, inclusion: { in: %w[THB USD] }
def charge!(token)
raise 'Payment invalid' unless valid?
Omise::Charge.create(
amount: (amount * 100).to_i,
currency: currency,
card: token
)
end
end
6. Test Mode Configurationโ
# config/environments/test.rb
Rails.application.configure do
config.after_initialize do
Omise.api_key = 'skey_test_123456789'
end
end
# Stub API calls in tests
RSpec.describe Payment do
before do
allow(Omise::Charge).to receive(:create).and_return(
double(id: 'chrg_test_123', paid: true)
)
end
end
Testingโ
RSpec Examplesโ
# spec/models/payment_spec.rb
require 'rails_helper'
RSpec.describe Payment, type: :model do
let(:payment) { create(:payment, amount: 1000) }
describe '#charge!' do
context 'with valid token' do
let(:charge) do
double(
id: 'chrg_test_123',
paid: true,
status: 'successful'
)
end
before do
allow(Omise::Charge).to receive(:create).and_return(charge)
end
it 'creates a charge' do
expect(Omise::Charge).to receive(:create).with(
hash_including(
amount: 100_000,
currency: 'THB'
)
)
payment.charge!('tokn_test_123')
end
it 'updates payment with charge ID' do
payment.charge!('tokn_test_123')
expect(payment.charge_id).to eq('chrg_test_123')
expect(payment.paid).to be true
end
end
context 'with card error' do
before do
allow(Omise::Charge).to receive(:create).and_raise(
Omise::CardError.new('insufficient_fund', 'Insufficient funds')
)
end
it 'raises CardError' do
expect { payment.charge!('tokn_test_123') }.to raise_error(Omise::CardError)
end
end
end
end
VCR for API Testingโ
# spec/spec_helper.rb
require 'vcr'
VCR.configure do |config|
config.cassette_library_dir = 'spec/vcr_cassettes'
config.hook_into :webmock
config.filter_sensitive_data('<OMISE_SECRET_KEY>') { ENV['OMISE_SECRET_KEY'] }
config.filter_sensitive_data('<OMISE_PUBLIC_KEY>') { ENV['OMISE_PUBLIC_KEY'] }
end
# spec/integration/charge_spec.rb
RSpec.describe 'Creating a charge' do
it 'creates a charge successfully', :vcr do
charge = Omise::Charge.create(
amount: 100_000,
currency: 'THB',
card: 'tokn_test_123'
)
expect(charge.paid).to be true
end
end
Mocking with WebMockโ
# spec/support/omise_stubs.rb
module OmiseStubs
def stub_charge_create(paid: true, amount: 100_000)
stub_request(:post, 'https://api.omise.co/charges')
.to_return(
status: 200,
body: {
object: 'charge',
id: 'chrg_test_123',
amount: amount,
currency: 'THB',
paid: paid
}.to_json,
headers: { 'Content-Type' => 'application/json' }
)
end
end
RSpec.configure do |config|
config.include OmiseStubs
end
# Usage in specs
RSpec.describe PaymentsController do
describe 'POST #create' do
before { stub_charge_create(paid: true) }
it 'creates a charge' do
post :create, params: { token: 'tokn_test_123', amount: 1000 }
expect(response).to redirect_to(success_path)
end
end
end
Troubleshootingโ
SSL Certificate Errorsโ
# If you encounter SSL errors in development
Omise.ssl_verify = false # Only for development/testing!
# Better: Update CA certificates
# On macOS with Homebrew:
# brew install curl-ca-bundle
# On Ubuntu/Debian:
# apt-get install ca-certificates
Connection Timeoutsโ
# Increase timeout for slow connections
Omise.timeout = 60 # seconds
# Or per-request
charge = Omise::Charge.create(
amount: 100_000,
currency: 'THB',
card: token,
timeout: 60
)
Debug Modeโ
# Enable debug logging
Omise.debug = true
# This will output:
# - Request URL and method
# - Request headers
# - Request body
# - Response status
# - Response body
Webhook Signature Verificationโ
# Verify webhook signatures
def verify_webhook_signature
payload = request.body.read
signature = request.headers['Omise-Signature']
expected_signature = OpenSSL::HMAC.hexdigest(
'sha256',
ENV['OMISE_WEBHOOK_SECRET'],
payload
)
unless Rack::Utils.secure_compare(signature, expected_signature)
render json: { error: 'Invalid signature' }, status: :unauthorized
end
end
FAQโ
How do I handle webhooks in Rails?โ
# config/routes.rb
post '/webhooks/omise', to: 'webhooks#omise'
# app/controllers/webhooks_controller.rb
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def omise
event = JSON.parse(request.body.read)
case event['key']
when 'charge.complete'
handle_charge_complete(event['data'])
when 'refund.create'
handle_refund_create(event['data'])
end
head :ok
end
private
def handle_charge_complete(charge_data)
charge = Omise::Charge.retrieve(charge_data['id'])
payment = Payment.find_by(charge_id: charge.id)
payment.update!(paid: charge.paid, status: charge.status)
end
end
How do I test payment flows without real charges?โ
# Use test API keys
Omise.api_key = 'skey_test_123456789'
# Use test card tokens
# Successful: tokn_test_5086xl7ddjbases4sq3i
# Declined: tokn_test_no1
# Check test mode
charge = Omise::Charge.create(...)
puts "Test mode: #{charge.livemode == false}"
How do I handle currency conversion?โ
# Store amounts in smallest currency unit
class Payment < ApplicationRecord
# amount stored in database as integer (satang/cents)
def amount_baht
amount / 100.0
end
def amount_baht=(value)
self.amount = (value.to_f * 100).to_i
end
end
# Usage
payment = Payment.new(amount_baht: 1000.00) # 1000.00 THB
payment.amount # => 100000 (in satang)
How do I implement retry logic?โ
# Use exponential backoff
def create_charge_with_retry(params, max_retries: 3)
retries = 0
begin
Omise::Charge.create(params)
rescue Omise::ConnectionError => e
retries += 1
if retries <= max_retries
sleep(2 ** retries) # 2, 4, 8 seconds
retry
else
raise
end
end
end
# Or use ActiveJob with retry
class ChargeJob < ApplicationJob
retry_on Omise::ConnectionError, wait: :exponentially_longer, attempts: 5
def perform(params)
Omise::Charge.create(params)
end
end
How do I handle partial refunds?โ
charge = Omise::Charge.retrieve('chrg_test_123')
# Check refundable amount
refundable = charge.amount - charge.refunded
# Create partial refund
if refundable >= 25_000
refund = Omise::Refund.create(
charge: charge.id,
amount: 25_000
)
end
# Check if fully refunded
charge.reload
puts "Fully refunded: #{charge.refunded == charge.amount}"
How do I save multiple cards for a customer?โ
customer = Omise::Customer.retrieve('cust_test_123')
# Add first card
customer.update(card: 'tokn_test_111')
# Add second card (replaces default)
customer.update(card: 'tokn_test_222')
# List all cards
customer.cards.each do |card|
puts "#{card.id}: #{card.brand} #{card.last_digits}"
end
# Charge specific card
Omise::Charge.create(
amount: 100_000,
currency: 'THB',
customer: customer.id,
card: 'card_test_111' # Specific card ID
)
How do I implement subscription billing?โ
# Create a customer with card
customer = Omise::Customer.create(
email: 'customer@example.com',
card: token
)
# Charge monthly
def charge_subscription(customer_id, plan_amount)
charge = Omise::Charge.create(
amount: (plan_amount * 100).to_i,
currency: 'THB',
customer: customer_id,
description: "Subscription #{Date.today.strftime('%B %Y')}"
)
if charge.paid
# Update subscription status
else
# Handle failed payment
end
end
# Schedule with whenever gem or cron
# Or use Sidekiq scheduler
class SubscriptionChargeJob < ApplicationJob
def perform
Subscription.active.find_each do |subscription|
charge_subscription(subscription.customer_id, subscription.amount)
end
end
end
Related Resourcesโ
- Omise API Documentation
- omise-ruby GitHub Repository
- Ruby API Reference
- Omise Dashboard
- Webhooks Guide
- Testing Guide
Next Stepsโ
Supportโ
If you encounter issues with the Ruby library:
- Check the GitHub Issues
- Review the API documentation
- Contact support@omise.co with:
- Ruby version
- omise-ruby gem version
- Error message and stack trace
- Steps to reproduce