Skip to main content

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

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

Next Stepsโ€‹

Supportโ€‹

If you encounter issues with the Ruby library:

  1. Check the GitHub Issues
  2. Review the API documentation
  3. Contact support@omise.co with:
    • Ruby version
    • omise-ruby gem version
    • Error message and stack trace
    • Steps to reproduce