Elixir Library (omise-elixir)
The omise-elixir library provides an idiomatic Elixir interface to the Omise API with pattern matching, OTP features, and excellent Phoenix integration.
Installationโ
Using Mixโ
Add to your mix.exs:
def deps do
[
{:omise, "~> 0.11"}
]
end
Then run:
mix deps.get
Requirementsโ
- Elixir 1.10 or higher (including Elixir 1.14+)
- Erlang/OTP 22+ (including OTP 25+)
- Mix for dependency management
- Phoenix 1.6+ (optional, for Phoenix applications)
Quick Startโ
Basic Configurationโ
# Configure your API keys
Omise.configure(
public_key: "pkey_test_123456789",
secret_key: "skey_test_123456789",
api_version: "2019-05-29"
)
Phoenix Configurationโ
# config/config.exs
config :omise,
public_key: System.get_env("OMISE_PUBLIC_KEY"),
secret_key: System.get_env("OMISE_SECRET_KEY"),
api_version: "2019-05-29"
# config/runtime.exs (Phoenix 1.6+)
import Config
config :omise,
public_key: System.fetch_env!("OMISE_PUBLIC_KEY"),
secret_key: System.fetch_env!("OMISE_SECRET_KEY")
Environment Variablesโ
# Development/Test
export OMISE_SECRET_KEY=skey_test_123456789
export OMISE_PUBLIC_KEY=pkey_test_123456789
# Production
# export OMISE_SECRET_KEY=skey_live_123456789
# export OMISE_PUBLIC_KEY=pkey_live_123456789
Common Operationsโ
Creating a Chargeโ
# Create a charge with a card token
{:ok, 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"
}
)
case charge do
%{paid: true} ->
IO.puts("Charge successful: #{charge.id}")
%{paid: false} ->
IO.puts("Charge failed: #{charge.failure_message}")
end
With Pattern Matchingโ
defmodule PaymentService do
def create_charge(token, amount) do
case Omise.Charge.create(amount: amount, currency: "THB", card: token) do
{:ok, %{paid: true} = charge} ->
{:ok, charge}
{:ok, %{paid: false, failure_message: message}} ->
{:error, message}
{:error, error} ->
{:error, error}
end
end
end
With 3D Secureโ
def create_secure_charge(token, amount, return_uri) do
case Omise.Charge.create(
amount: amount,
currency: "THB",
card: token,
return_uri: return_uri
) do
{:ok, %{authorized: true, authorize_uri: uri} = charge} when not is_nil(uri) ->
{:redirect, uri}
{:ok, %{authorized: true, paid: true} = charge} ->
{:success, charge}
{:ok, %{failure_message: message}} ->
{:error, message}
{:error, error} ->
{:error, error}
end
end
Retrieving a Chargeโ
case Omise.Charge.retrieve("chrg_test_123") do
{:ok, charge} ->
IO.puts("Amount: #{charge.amount}")
IO.puts("Currency: #{charge.currency}")
IO.puts("Status: #{charge.status}")
IO.puts("Paid: #{charge.paid}")
{:error, error} ->
IO.puts("Error: #{inspect(error)}")
end
Listing Chargesโ
# List all charges with pagination
{:ok, charges} = Omise.Charge.list(
limit: 20,
offset: 0,
order: "reverse_chronological"
)
charges.data
|> Enum.filter(&(&1.paid))
|> Enum.each(fn charge ->
IO.puts("#{charge.id}: #{charge.amount} #{charge.currency}")
end)
# List charges with date filters
week_ago = DateTime.utc_now() |> DateTime.add(-7, :day) |> DateTime.to_date()
today = Date.utc_today()
{:ok, recent_charges} = Omise.Charge.list(
from: week_ago,
to: today
)
Creating a Customerโ
{:ok, customer} = Omise.Customer.create(
email: "customer@example.com",
description: "John Doe",
metadata: %{
user_id: "12345",
account_type: "premium"
}
)
IO.puts("Customer created: #{customer.id}")
Saving a Card to a Customerโ
# Update customer with a card token
{:ok, customer} = Omise.Customer.update(
"cust_test_123",
card: "tokn_test_456"
)
IO.puts("Card saved: #{customer.default_card}")
# Or create customer with card in one step
{:ok, customer} = Omise.Customer.create(
email: "customer@example.com",
description: "John Doe",
card: "tokn_test_123"
)
Listing Customer Cardsโ
{:ok, customer} = Omise.Customer.retrieve("cust_test_123")
customer.cards.data
|> Enum.each(fn card ->
IO.puts("#{card.brand} ending in #{card.last_digits}")
IO.puts("Expires: #{card.expiration_month}/#{card.expiration_year}")
end)
Creating a Refundโ
# Full refund
{:ok, refund} = Omise.Refund.create(
"chrg_test_123",
%{}
)
# Partial refund
{:ok, refund} = Omise.Refund.create(
"chrg_test_123",
%{
amount: 25_000, # 250.00 THB
metadata: %{
reason: "customer_request",
ticket_id: "TICKET-123"
}
}
)
IO.puts("Refund #{refund.id}: #{refund.amount} #{refund.currency}")
Creating a Transferโ
{:ok, transfer} = Omise.Transfer.create(
amount: 500_000, # 5,000.00 THB
recipient: "recp_test_123",
metadata: %{
payout_id: "PAYOUT-456"
}
)
IO.puts("Transfer #{transfer.id}: #{transfer.amount}")
Alternative Payment Methodsโ
Creating a Sourceโ
# Prompt Pay QR
{:ok, source} = Omise.Source.create(
type: "promptpay",
amount: 100_000,
currency: "THB"
)
IO.puts("QR Code URL: #{source.scannable_code.image.download_uri}")
# Create charge with source
{:ok, charge} = Omise.Charge.create(
amount: 100_000,
currency: "THB",
source: source.id,
return_uri: "https://example.com/payment/callback"
)
Internet Bankingโ
{:ok, source} = Omise.Source.create(
type: "internet_banking_scb",
amount: 100_000,
currency: "THB"
)
{:ok, charge} = Omise.Charge.create(
amount: 100_000,
currency: "THB",
source: source.id,
return_uri: "https://example.com/payment/callback"
)
# Redirect customer to charge.authorize_uri
Mobile Bankingโ
{:ok, source} = Omise.Source.create(
type: "mobile_banking_scb",
amount: 100_000,
currency: "THB"
)
{:ok, charge} = Omise.Charge.create(
amount: 100_000,
currency: "THB",
source: source.id,
return_uri: "https://example.com/payment/callback"
)
Installmentsโ
{:ok, source} = Omise.Source.create(
type: "installment_kbank",
amount: 100_000,
currency: "THB",
installment_term: 6 # 6 months
)
{:ok, charge} = Omise.Charge.create(
amount: 100_000,
currency: "THB",
source: source.id,
return_uri: "https://example.com/payment/callback"
)
Error Handlingโ
defmodule PaymentService do
def create_charge_with_error_handling(params) do
case Omise.Charge.create(params) do
{:ok, charge} ->
{:ok, charge}
{:error, %{code: "authentication_failure"}} ->
{:error, "Invalid API key"}
{:error, %{code: "invalid_card"}} ->
{:error, "Card was declined"}
{:error, %{code: "insufficient_fund"}} ->
{:error, "Insufficient funds"}
{:error, error} ->
{:error, "Payment failed: #{inspect(error)}"}
end
end
end
Custom Error Handlerโ
defmodule PaymentErrorHandler do
@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"
}
def get_error_message({:error, %{code: code}}), do: Map.get(@error_messages, code, "Unknown error")
def get_error_message({:error, error}), do: "Error: #{inspect(error)}"
def get_error_message(_), do: "Unknown error"
end
Phoenix Integrationโ
Controller Exampleโ
defmodule MyAppWeb.PaymentController do
use MyAppWeb, :controller
alias MyApp.Payments
alias MyApp.Orders
def create(conn, %{"order_id" => order_id, "omise_token" => token}) do
with {:ok, order} <- Orders.get_order(order_id),
{:ok, charge} <- create_charge(order, token),
{:ok, payment} <- Payments.save_payment(charge, order) do
handle_charge_result(conn, charge, order)
else
{:error, :not_found} ->
conn
|> put_flash(:error, "Order not found")
|> redirect(to: Routes.order_path(conn, :index))
{:error, reason} ->
conn
|> put_flash(:error, "Payment failed: #{reason}")
|> redirect(to: Routes.payment_path(conn, :new, order_id))
end
end
defp create_charge(order, token) do
Omise.Charge.create(
amount: round(order.total * 100),
currency: "THB",
card: token,
description: "Order ##{order.id}",
metadata: %{
order_id: order.id,
customer_email: order.email
},
return_uri: Routes.payment_url(MyAppWeb.Endpoint, :callback)
)
end
defp handle_charge_result(conn, %{paid: true} = charge, order) do
Orders.mark_paid(order)
conn
|> put_flash(:info, "Payment successful!")
|> redirect(to: Routes.order_path(conn, :show, order))
end
defp handle_charge_result(conn, %{authorize_uri: uri} = _charge, _order) when not is_nil(uri) do
redirect(conn, external: uri)
end
defp handle_charge_result(conn, %{failure_message: message}, order) do
conn
|> put_flash(:error, message)
|> redirect(to: Routes.payment_path(conn, :new, order))
end
def callback(conn, %{"id" => charge_id}) do
with {:ok, charge} <- Omise.Charge.retrieve(charge_id),
{:ok, payment} <- Payments.get_by_charge_id(charge_id),
{:ok, _payment} <- Payments.update_status(payment, charge) do
if charge.paid do
conn
|> put_flash(:info, "Payment successful!")
|> redirect(to: Routes.order_path(conn, :show, payment.order_id))
else
conn
|> put_flash(:error, charge.failure_message)
|> redirect(to: Routes.payment_path(conn, :new, payment.order_id))
end
else
{:error, _reason} ->
conn
|> put_flash(:error, "Payment verification failed")
|> redirect(to: Routes.page_path(conn, :index))
end
end
end
Context Moduleโ
defmodule MyApp.Payments do
import Ecto.Query
alias MyApp.Repo
alias MyApp.Payments.Payment
def save_payment(charge, order) do
%Payment{}
|> Payment.changeset(%{
order_id: order.id,
charge_id: charge.id,
amount: Decimal.div(charge.amount, 100),
currency: charge.currency,
status: charge.status,
paid: charge.paid
})
|> Repo.insert()
end
def get_by_charge_id(charge_id) do
case Repo.get_by(Payment, charge_id: charge_id) do
nil -> {:error, :not_found}
payment -> {:ok, payment}
end
end
def update_status(payment, charge) do
payment
|> Payment.changeset(%{
status: charge.status,
paid: charge.paid,
failure_code: charge.failure_code,
failure_message: charge.failure_message
})
|> Repo.update()
end
def charge_customer(customer_id, amount) do
Omise.Charge.create(
amount: round(amount * 100),
currency: "THB",
customer: customer_id
)
end
def refund_charge(charge_id, amount \\ nil) do
params = if amount, do: %{amount: round(amount * 100)}, else: %{}
Omise.Refund.create(charge_id, params)
end
end
Schemaโ
defmodule MyApp.Payments.Payment do
use Ecto.Schema
import Ecto.Changeset
schema "payments" do
field :order_id, :id
field :charge_id, :string
field :amount, :decimal
field :currency, :string, default: "THB"
field :status, :string
field :paid, :boolean, default: false
field :failure_code, :string
field :failure_message, :string
field :refund_id, :string
field :refund_amount, :decimal
timestamps()
end
def changeset(payment, attrs) do
payment
|> cast(attrs, [
:order_id, :charge_id, :amount, :currency, :status,
:paid, :failure_code, :failure_message, :refund_id, :refund_amount
])
|> validate_required([:order_id, :amount, :currency, :status])
|> validate_number(:amount, greater_than: 0)
|> unique_constraint(:charge_id)
end
end
GenServer for Background Processingโ
Payment Processorโ
defmodule MyApp.PaymentProcessor do
use GenServer
require Logger
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def process_charge(token, amount, metadata) do
GenServer.call(__MODULE__, {:process_charge, token, amount, metadata})
end
@impl true
def init(_opts) do
{:ok, %{}}
end
@impl true
def handle_call({:process_charge, token, amount, metadata}, _from, state) do
result = case Omise.Charge.create(
amount: amount,
currency: "THB",
card: token,
metadata: metadata
) do
{:ok, %{paid: true} = charge} ->
Logger.info("Charge successful: #{charge.id}")
{:ok, charge}
{:ok, %{paid: false, failure_message: message} = charge} ->
Logger.error("Charge failed: #{message}")
{:error, message}
{:error, error} ->
Logger.error("Charge error: #{inspect(error)}")
{:error, error}
end
{:reply, result, state}
end
end
Subscription Managerโ
defmodule MyApp.SubscriptionManager do
use GenServer
require Logger
alias MyApp.Repo
alias MyApp.Subscriptions.Subscription
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@impl true
def init(_opts) do
schedule_work()
{:ok, %{}}
end
@impl true
def handle_info(:charge_subscriptions, state) do
charge_all_subscriptions()
schedule_work()
{:noreply, state}
end
defp schedule_work do
# Schedule for next day at midnight
next_run = calculate_next_midnight()
Process.send_after(self(), :charge_subscriptions, next_run)
end
defp charge_all_subscriptions do
Subscription
|> where([s], s.status == "active")
|> Repo.all()
|> Enum.each(&charge_subscription/1)
end
defp charge_subscription(subscription) do
case Omise.Charge.create(
amount: subscription.plan_amount,
currency: "THB",
customer: subscription.customer_id,
description: "Subscription #{Date.utc_today()}"
) do
{:ok, %{paid: true}} ->
subscription
|> Ecto.Changeset.change(last_charge_date: DateTime.utc_now())
|> Repo.update()
Logger.info("Charged subscription #{subscription.id}")
{:error, error} ->
Logger.error("Failed to charge subscription #{subscription.id}: #{inspect(error)}")
end
end
defp calculate_next_midnight do
now = DateTime.utc_now()
tomorrow = DateTime.add(now, 1, :day)
midnight = %{tomorrow | hour: 0, minute: 0, second: 0, microsecond: {0, 0}}
DateTime.diff(midnight, now, :millisecond)
end
end
Task for Async Operationsโ
defmodule MyApp.AsyncPayment do
def process_charges_async(charge_params_list) do
charge_params_list
|> Enum.map(fn params ->
Task.async(fn -> create_charge(params) end)
end)
|> Enum.map(&Task.await/1)
end
defp create_charge(params) do
case Omise.Charge.create(params) do
{:ok, charge} -> {:ok, charge}
{:error, error} -> {:error, error}
end
end
end
# Usage
charge_params = [
%{amount: 100_000, currency: "THB", card: "tokn_test_1"},
%{amount: 200_000, currency: "THB", card: "tokn_test_2"}
]
results = MyApp.AsyncPayment.process_charges_async(charge_params)
Best Practicesโ
1. Use Pattern Matchingโ
def process_payment(token, amount) do
case Omise.Charge.create(amount: amount, currency: "THB", card: token) do
{:ok, %{paid: true, id: charge_id}} ->
# Handle successful payment
{:ok, charge_id}
{:ok, %{authorize_uri: uri}} when not is_nil(uri) ->
# Handle 3D Secure redirect
{:redirect, uri}
{:ok, %{failure_message: message}} ->
# Handle failed payment
{:error, message}
{:error, %{code: code, message: message}} ->
# Handle API error
{:error, "#{code}: #{message}"}
end
end
2. Use Pipes for Data Transformationโ
def list_paid_charges do
{:ok, charges} = Omise.Charge.list(limit: 100)
charges.data
|> Enum.filter(&(&1.paid))
|> Enum.map(&extract_charge_info/1)
|> Enum.sort_by(& &1.created, :desc)
end
defp extract_charge_info(charge) do
%{
id: charge.id,
amount: charge.amount / 100,
currency: charge.currency,
created: charge.created
}
end
3. Use Structs for Type Safetyโ
defmodule MyApp.Money do
defstruct amount: 0, currency: "THB"
def new(amount, currency \\ "THB") do
%__MODULE__{amount: round(amount * 100), currency: currency}
end
def to_baht(%__MODULE__{amount: amount}), do: amount / 100
end
# Usage
money = MyApp.Money.new(1000.00)
{:ok, charge} = Omise.Charge.create(
amount: money.amount,
currency: money.currency,
card: token
)
4. Implement Idempotencyโ
def create_idempotent_charge(params, order_id) do
idempotency_key = "order-#{order_id}-#{:os.system_time(:millisecond)}"
Omise.Charge.create(
params,
headers: [{"Idempotency-Key", idempotency_key}]
)
end
5. Use Supervisors for Fault Toleranceโ
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
MyApp.Repo,
MyAppWeb.Endpoint,
MyApp.PaymentProcessor,
MyApp.SubscriptionManager
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
6. Use Telemetry for Monitoringโ
defmodule MyApp.PaymentTelemetry do
def handle_event([:omise, :charge, :create], measurements, metadata, _config) do
Logger.info("Charge created",
charge_id: metadata.charge_id,
amount: metadata.amount,
duration: measurements.duration
)
end
end
# Attach handler
:telemetry.attach(
"payment-handler",
[:omise, :charge, :create],
&MyApp.PaymentTelemetry.handle_event/4,
nil
)
Testingโ
ExUnit Testsโ
defmodule MyApp.PaymentsTest do
use MyApp.DataCase
alias MyApp.Payments
import Mox
setup :verify_on_exit!
describe "create_charge/2" do
test "creates a successful charge" do
expect(OmiseMock, :create_charge, fn _params ->
{:ok, %{
id: "chrg_test_123",
amount: 100_000,
currency: "THB",
paid: true,
status: "successful"
}}
end)
assert {:ok, charge} = Payments.create_charge("tokn_test_123", 1000.00)
assert charge.id == "chrg_test_123"
assert charge.paid == true
end
test "handles charge failure" do
expect(OmiseMock, :create_charge, fn _params ->
{:error, %{code: "insufficient_fund", message: "Insufficient funds"}}
end)
assert {:error, error} = Payments.create_charge("tokn_test_123", 1000.00)
assert error.code == "insufficient_fund"
end
end
end
Integration Testsโ
defmodule MyAppWeb.PaymentControllerTest do
use MyAppWeb.ConnCase
alias MyApp.Orders
setup do
order = Orders.create_order!(%{total: 1000.00, email: "test@example.com"})
{:ok, order: order}
end
describe "POST /payment/:order_id" do
@tag :integration
test "creates a payment successfully", %{conn: conn, order: order} do
# Use test token
token = "tokn_test_5086xl7ddjbases4sq3i"
conn = post(conn, Routes.payment_path(conn, :create, order.id), %{
"omise_token" => token
})
assert redirected_to(conn) == Routes.order_path(conn, :show, order)
assert get_flash(conn, :info) == "Payment successful!"
end
end
end
Mock for Testingโ
# test/support/mocks.ex
Mox.defmock(OmiseMock, for: MyApp.PaymentBehaviour)
# lib/my_app/payment_behaviour.ex
defmodule MyApp.PaymentBehaviour do
@callback create_charge(map()) :: {:ok, map()} | {:error, map()}
@callback retrieve_charge(String.t()) :: {:ok, map()} | {:error, map()}
end
# Use in application
defmodule MyApp.Payments do
@payment_client Application.compile_env(:my_app, :payment_client, Omise)
def create_charge(params) do
@payment_client.Charge.create(params)
end
end
# In test config
config :my_app, payment_client: OmiseMock
Troubleshootingโ
Connection Timeoutโ
# Configure HTTP client timeout
config :omise,
http_options: [timeout: 60_000, recv_timeout: 60_000]
Debug Loggingโ
# Enable debug logging
config :logger, level: :debug
config :omise, debug: true
Webhook Signature Verificationโ
defmodule MyAppWeb.WebhookController do
use MyAppWeb, :controller
def omise(conn, _params) do
with {:ok, payload} <- read_body(conn),
{:ok, signature} <- get_signature(conn),
true <- verify_signature(payload, signature) do
payload
|> Jason.decode!()
|> handle_webhook()
json(conn, %{status: "ok"})
else
false ->
conn
|> put_status(:unauthorized)
|> json(%{error: "Invalid signature"})
{:error, _reason} ->
conn
|> put_status(:bad_request)
|> json(%{error: "Invalid request"})
end
end
defp get_signature(conn) do
case get_req_header(conn, "omise-signature") do
[signature] -> {:ok, signature}
_ -> {:error, :no_signature}
end
end
defp verify_signature(payload, signature) do
secret = Application.get_env(:omise, :webhook_secret)
expected = :crypto.mac(:hmac, :sha256, secret, payload) |> Base.encode16(case: :lower)
Plug.Crypto.secure_compare(signature, expected)
end
defp handle_webhook(%{"key" => "charge.complete", "data" => data}) do
# Handle charge completion
end
defp handle_webhook(%{"key" => "refund.create", "data" => data}) do
# Handle refund creation
end
defp handle_webhook(_), do: :ok
end
FAQโ
How do I handle webhooks in Phoenix?โ
defmodule MyAppWeb.WebhookController do
use MyAppWeb, :controller
def omise(conn, _params) do
{:ok, body, conn} = read_body(conn)
event = Jason.decode!(body)
case event["key"] do
"charge.complete" ->
handle_charge_complete(event["data"])
"refund.create" ->
handle_refund_create(event["data"])
end
json(conn, %{status: "ok"})
end
defp handle_charge_complete(%{"id" => charge_id}) do
{:ok, charge} = Omise.Charge.retrieve(charge_id)
MyApp.Payments.update_charge_status(charge)
end
end
How do I test without real charges?โ
# Use test API keys in config/test.exs
config :omise,
public_key: "pkey_test_123456789",
secret_key: "skey_test_123456789"
# Use test tokens
{:ok, charge} = Omise.Charge.create(
amount: 100_000,
currency: "THB",
card: "tokn_test_5086xl7ddjbases4sq3i"
)
# Verify test mode
assert charge.livemode == false
How do I handle partial refunds?โ
def refund_charge(charge_id, refund_amount \\ nil) do
with {:ok, charge} <- Omise.Charge.retrieve(charge_id) do
refundable = charge.amount - charge.refunded
params = case refund_amount do
nil -> %{}
amount when amount <= refundable -> %{amount: round(amount * 100)}
_ -> {:error, :amount_exceeds_refundable}
end
case params do
{:error, reason} -> {:error, reason}
_ -> Omise.Refund.create(charge_id, params)
end
end
end
How do I save multiple cards for a customer?โ
def add_card_to_customer(customer_id, token) do
Omise.Customer.update(customer_id, card: token)
end
def list_customer_cards(customer_id) do
with {:ok, customer} <- Omise.Customer.retrieve(customer_id) do
{:ok, customer.cards.data}
end
end
def charge_specific_card(customer_id, card_id, amount) do
Omise.Charge.create(
amount: round(amount * 100),
currency: "THB",
customer: customer_id,
card: card_id
)
end
How do I implement subscription billing?โ
defmodule MyApp.SubscriptionBilling do
def charge_monthly(customer_id, plan_amount) do
Omise.Charge.create(
amount: plan_amount,
currency: "THB",
customer: customer_id,
description: "Subscription #{Date.utc_today()}"
)
end
end
# Use with Quantum for scheduling
config :my_app, MyApp.Scheduler,
jobs: [
{"0 0 1 * *", {MyApp.SubscriptionBilling, :charge_all, []}}
]
How do I implement retry logic?โ
def create_charge_with_retry(params, retries \\ 3) do
case Omise.Charge.create(params) do
{:ok, charge} ->
{:ok, charge}
{:error, _error} when retries > 0 ->
:timer.sleep(:math.pow(2, 3 - retries) * 1000 |> round())
create_charge_with_retry(params, retries - 1)
{:error, error} ->
{:error, error}
end
end
Related Resourcesโ
- Omise API Documentation
- omise-elixir GitHub Repository
- Hex Package
- Omise Dashboard
- Webhooks Guide
- Testing Guide
Next Stepsโ
Supportโ
If you encounter issues with the Elixir library:
- Check the GitHub Issues
- Review the API documentation
- Contact support@omise.co with:
- Elixir version
- omise-elixir library version
- Error message and stack trace
- Steps to reproduce