Skip to main content

Go Library (omise-go)

The omise-go library provides an idiomatic Go interface to the Omise API with support for goroutines, channels, context-aware operations, and modern Go best practices.

Installationโ€‹

Using go getโ€‹

go get github.com/omise/omise-go/v2

Using go.modโ€‹

require github.com/omise/omise-go/v2 v2.0.0

Requirementsโ€‹

  • Go 1.16 or higher (including Go 1.20+)
  • Go modules for dependency management

Quick Startโ€‹

Basic Configurationโ€‹

package main

import (
"github.com/omise/omise-go/v2"
"github.com/omise/omise-go/v2/operations"
)

func main() {
client, err := omise.NewClient(
"pkey_test_123456789",
"skey_test_123456789",
)
if err != nil {
panic(err)
}
}

With Environment Variablesโ€‹

import (
"os"
"github.com/omise/omise-go/v2"
)

func initClient() (*omise.Client, error) {
return omise.NewClient(
os.Getenv("OMISE_PUBLIC_KEY"),
os.Getenv("OMISE_SECRET_KEY"),
)
}

With Configuration Structโ€‹

type Config struct {
OmisePublicKey string
OmiseSecretKey string
APIVersion string
}

func NewOmiseClient(config Config) (*omise.Client, error) {
client, err := omise.NewClient(
config.OmisePublicKey,
config.OmiseSecretKey,
)
if err != nil {
return nil, err
}

client.SetAPIVersion(config.APIVersion)
return client, nil
}

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

package main

import (
"context"
"fmt"
"github.com/omise/omise-go/v2"
"github.com/omise/omise-go/v2/operations"
)

func createCharge(client *omise.Client, token string, amount int64) (*omise.Charge, error) {
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: amount, // 1,000.00 THB = 100000 satang
Currency: "THB",
Card: token,
Description: "Order #1234",
Metadata: map[string]interface{}{
"order_id": "1234",
"customer_name": "John Doe",
},
}

if err := client.Do(charge, createCharge); err != nil {
return nil, fmt.Errorf("charge creation failed: %w", err)
}

if charge.Paid {
fmt.Printf("Charge successful: %s\n", charge.ID)
} else {
fmt.Printf("Charge failed: %s\n", charge.FailureMessage)
}

return charge, nil
}

With Contextโ€‹

import "context"

func createChargeWithContext(
ctx context.Context,
client *omise.Client,
token string,
amount int64,
) (*omise.Charge, error) {
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: amount,
Currency: "THB",
Card: token,
}

if err := client.DoWithContext(ctx, charge, createCharge); err != nil {
return nil, err
}

return charge, nil
}

// Usage with timeout
func main() {
client, _ := omise.NewClient("pkey", "skey")

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

charge, err := createChargeWithContext(ctx, client, "tokn_test_123", 100000)
if err != nil {
log.Fatal(err)
}
}

With 3D Secureโ€‹

type ChargeResult struct {
Charge *omise.Charge
RedirectURI string
}

func createSecureCharge(
client *omise.Client,
token string,
amount int64,
returnURI string,
) (*ChargeResult, error) {
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: amount,
Currency: "THB",
Card: token,
ReturnURI: returnURI,
}

if err := client.Do(charge, createCharge); err != nil {
return nil, err
}

if charge.Authorized {
if charge.AuthorizeURI != "" {
return &ChargeResult{RedirectURI: charge.AuthorizeURI}, nil
}
return &ChargeResult{Charge: charge}, nil
}

return nil, fmt.Errorf("charge failed: %s", charge.FailureMessage)
}

Retrieving a Chargeโ€‹

func getCharge(client *omise.Client, chargeID string) (*omise.Charge, error) {
charge, retrieve := &omise.Charge{}, &operations.RetrieveCharge{
ChargeID: chargeID,
}

if err := client.Do(charge, retrieve); err != nil {
return nil, err
}

fmt.Printf("Amount: %d\n", charge.Amount)
fmt.Printf("Currency: %s\n", charge.Currency)
fmt.Printf("Status: %s\n", charge.Status)
fmt.Printf("Paid: %t\n", charge.Paid)

return charge, nil
}

Listing Chargesโ€‹

func listCharges(client *omise.Client) ([]*omise.Charge, error) {
chargeList := &omise.ChargeList{}
listOp := &operations.ListCharges{
List: operations.List{
Limit: 20,
Offset: 0,
Order: operations.ReverseChronological,
},
}

if err := client.Do(chargeList, listOp); err != nil {
return nil, err
}

return chargeList.Data, nil
}

// With date filtering
func listRecentCharges(client *omise.Client) ([]*omise.Charge, error) {
weekAgo := time.Now().AddDate(0, 0, -7)

chargeList := &omise.ChargeList{}
listOp := &operations.ListCharges{
List: operations.List{
From: weekAgo,
To: time.Now(),
},
}

if err := client.Do(chargeList, listOp); err != nil {
return nil, err
}

// Filter paid charges
var paidCharges []*omise.Charge
for _, charge := range chargeList.Data {
if charge.Paid {
paidCharges = append(paidCharges, charge)
}
}

return paidCharges, nil
}

Creating a Customerโ€‹

func createCustomer(
client *omise.Client,
email, description string,
) (*omise.Customer, error) {
customer, createOp := &omise.Customer{}, &operations.CreateCustomer{
Email: email,
Description: description,
Metadata: map[string]interface{}{
"user_id": "12345",
"account_type": "premium",
},
}

if err := client.Do(customer, createOp); err != nil {
return nil, err
}

fmt.Printf("Customer created: %s\n", customer.ID)
return customer, nil
}

Saving a Card to a Customerโ€‹

func addCardToCustomer(
client *omise.Client,
customerID, token string,
) (*omise.Customer, error) {
customer, updateOp := &omise.Customer{}, &operations.UpdateCustomer{
CustomerID: customerID,
Card: token,
}

if err := client.Do(customer, updateOp); err != nil {
return nil, err
}

fmt.Printf("Card saved: %s\n", customer.DefaultCard)
return customer, nil
}

// Create customer with card
func createCustomerWithCard(
client *omise.Client,
email, token string,
) (*omise.Customer, error) {
customer, createOp := &omise.Customer{}, &operations.CreateCustomer{
Email: email,
Card: token,
}

if err := client.Do(customer, createOp); err != nil {
return nil, err
}

return customer, nil
}

Listing Customer Cardsโ€‹

func listCustomerCards(client *omise.Client, customerID string) ([]*omise.Card, error) {
customer, retrieve := &omise.Customer{}, &operations.RetrieveCustomer{
CustomerID: customerID,
}

if err := client.Do(customer, retrieve); err != nil {
return nil, err
}

for _, card := range customer.Cards.Data {
fmt.Printf("%s ending in %s\n", card.Brand, card.LastDigits)
fmt.Printf("Expires: %d/%d\n", card.ExpirationMonth, card.ExpirationYear)
}

return customer.Cards.Data, nil
}

Creating a Refundโ€‹

// Full refund
func refundCharge(client *omise.Client, chargeID string) (*omise.Refund, error) {
refund, createOp := &omise.Refund{}, &operations.CreateRefund{
ChargeID: chargeID,
}

if err := client.Do(refund, createOp); err != nil {
return nil, err
}

return refund, nil
}

// Partial refund
func partialRefund(
client *omise.Client,
chargeID string,
amount int64,
) (*omise.Refund, error) {
refund, createOp := &omise.Refund{}, &operations.CreateRefund{
ChargeID: chargeID,
Amount: amount,
Metadata: map[string]interface{}{
"reason": "customer_request",
"ticket_id": "TICKET-123",
},
}

if err := client.Do(refund, createOp); err != nil {
return nil, err
}

fmt.Printf("Refund %s: %d %s\n", refund.ID, refund.Amount, refund.Currency)
return refund, nil
}

Creating a Transferโ€‹

func createTransfer(
client *omise.Client,
amount int64,
recipientID string,
) (*omise.Transfer, error) {
transfer, createOp := &omise.Transfer{}, &operations.CreateTransfer{
Amount: amount,
Recipient: recipientID,
Metadata: map[string]interface{}{
"payout_id": "PAYOUT-456",
},
}

if err := client.Do(transfer, createOp); err != nil {
return nil, err
}

fmt.Printf("Transfer %s: %d\n", transfer.ID, transfer.Amount)
return transfer, nil
}

Alternative Payment Methodsโ€‹

Creating a Sourceโ€‹

// Prompt Pay QR
func createPromptPaySource(client *omise.Client, amount int64) (*omise.Source, error) {
source, createOp := &omise.Source{}, &operations.CreateSource{
Type: "promptpay",
Amount: amount,
Currency: "THB",
}

if err := client.Do(source, createOp); err != nil {
return nil, err
}

fmt.Printf("QR Code URL: %s\n", source.ScannableCode.Image.DownloadURI)

// Create charge with source
charge, chargeOp := &omise.Charge{}, &operations.CreateCharge{
Amount: amount,
Currency: "THB",
Source: source.ID,
ReturnURI: "https://example.com/payment/callback",
}

if err := client.Do(charge, chargeOp); err != nil {
return nil, err
}

return source, nil
}

Internet Bankingโ€‹

func createInternetBankingCharge(
client *omise.Client,
amount int64,
bank string,
) (*omise.Charge, error) {
source, sourceOp := &omise.Source{}, &operations.CreateSource{
Type: "internet_banking_" + bank,
Amount: amount,
Currency: "THB",
}

if err := client.Do(source, sourceOp); err != nil {
return nil, err
}

charge, chargeOp := &omise.Charge{}, &operations.CreateCharge{
Amount: amount,
Currency: "THB",
Source: source.ID,
ReturnURI: "https://example.com/payment/callback",
}

if err := client.Do(charge, chargeOp); err != nil {
return nil, err
}

// Redirect to charge.AuthorizeURI
return charge, nil
}

Installmentsโ€‹

func createInstallmentCharge(
client *omise.Client,
amount int64,
bank string,
term int,
) (*omise.Charge, error) {
source, sourceOp := &omise.Source{}, &operations.CreateSource{
Type: "installment_" + bank,
Amount: amount,
Currency: "THB",
InstallmentTerm: term,
}

if err := client.Do(source, sourceOp); err != nil {
return nil, err
}

charge, chargeOp := &omise.Charge{}, &operations.CreateCharge{
Amount: amount,
Currency: "THB",
Source: source.ID,
ReturnURI: "https://example.com/payment/callback",
}

if err := client.Do(charge, chargeOp); err != nil {
return nil, err
}

return charge, nil
}

Error Handlingโ€‹

import (
"errors"
"github.com/omise/omise-go/v2"
)

func createChargeWithErrorHandling(
client *omise.Client,
token string,
amount int64,
) (*omise.Charge, error) {
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: amount,
Currency: "THB",
Card: token,
}

if err := client.Do(charge, createCharge); err != nil {
var omiseErr *omise.Error
if errors.As(err, &omiseErr) {
switch omiseErr.Code {
case "authentication_failure":
return nil, errors.New("invalid API key")
case "invalid_card":
return nil, errors.New("card was declined")
case "insufficient_fund":
return nil, errors.New("insufficient funds")
default:
return nil, fmt.Errorf("omise error: %s", omiseErr.Message)
}
}
return nil, fmt.Errorf("request failed: %w", err)
}

return charge, nil
}

Custom Error Handlerโ€‹

type PaymentErrorHandler struct {
errorMessages map[string]string
}

func NewPaymentErrorHandler() *PaymentErrorHandler {
return &PaymentErrorHandler{
errorMessages: map[string]string{
"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",
},
}
}

func (h *PaymentErrorHandler) GetErrorMessage(err error) string {
var omiseErr *omise.Error
if errors.As(err, &omiseErr) {
if msg, ok := h.errorMessages[omiseErr.Code]; ok {
return msg
}
return omiseErr.Message
}
return err.Error()
}

HTTP Server Integrationโ€‹

Handler Exampleโ€‹

package main

import (
"encoding/json"
"net/http"
"github.com/omise/omise-go/v2"
"github.com/omise/omise-go/v2/operations"
)

type PaymentHandler struct {
client *omise.Client
paymentService *PaymentService
}

func NewPaymentHandler(client *omise.Client, service *PaymentService) *PaymentHandler {
return &PaymentHandler{
client: client,
paymentService: service,
}
}

func (h *PaymentHandler) CreatePayment(w http.ResponseWriter, r *http.Request) {
var req struct {
OrderID string `json:"order_id"`
OmiseToken string `json:"omise_token"`
}

if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}

order, err := h.paymentService.GetOrder(req.OrderID)
if err != nil {
http.Error(w, "Order not found", http.StatusNotFound)
return
}

charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: int64(order.Total * 100),
Currency: "THB",
Card: req.OmiseToken,
Description: "Order #" + order.ID,
Metadata: map[string]interface{}{
"order_id": order.ID,
"customer_email": order.Email,
},
ReturnURI: "https://example.com/payment/callback",
}

if err := h.client.Do(charge, createCharge); err != nil {
http.Error(w, "Payment failed", http.StatusInternalServerError)
return
}

// Save payment
payment := &Payment{
OrderID: order.ID,
ChargeID: charge.ID,
Amount: order.Total,
Status: charge.Status,
Paid: charge.Paid,
}

if err := h.paymentService.SavePayment(payment); err != nil {
http.Error(w, "Failed to save payment", http.StatusInternalServerError)
return
}

if charge.Paid {
h.paymentService.MarkOrderPaid(order.ID)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"charge": charge,
})
} else if charge.AuthorizeURI != "" {
json.NewEncoder(w).Encode(map[string]interface{}{
"redirect_uri": charge.AuthorizeURI,
})
} else {
http.Error(w, charge.FailureMessage, http.StatusBadRequest)
}
}

func (h *PaymentHandler) PaymentCallback(w http.ResponseWriter, r *http.Request) {
chargeID := r.URL.Query().Get("id")

charge, retrieve := &omise.Charge{}, &operations.RetrieveCharge{
ChargeID: chargeID,
}

if err := h.client.Do(charge, retrieve); err != nil {
http.Error(w, "Payment verification failed", http.StatusInternalServerError)
return
}

if err := h.paymentService.UpdatePaymentStatus(charge.ID, charge.Status, charge.Paid); err != nil {
http.Error(w, "Failed to update payment", http.StatusInternalServerError)
return
}

if charge.Paid {
http.Redirect(w, r, "/orders/"+charge.Metadata["order_id"].(string)+"?success=true", http.StatusSeeOther)
} else {
http.Redirect(w, r, "/payment?error="+charge.FailureMessage, http.StatusSeeOther)
}
}

Service Layerโ€‹

type PaymentService struct {
client *omise.Client
db *Database
}

func NewPaymentService(client *omise.Client, db *Database) *PaymentService {
return &PaymentService{
client: client,
db: db,
}
}

func (s *PaymentService) GetOrder(orderID string) (*Order, error) {
return s.db.GetOrder(orderID)
}

func (s *PaymentService) SavePayment(payment *Payment) error {
return s.db.SavePayment(payment)
}

func (s *PaymentService) UpdatePaymentStatus(chargeID, status string, paid bool) error {
return s.db.UpdatePaymentStatus(chargeID, status, paid)
}

func (s *PaymentService) MarkOrderPaid(orderID string) error {
return s.db.UpdateOrderStatus(orderID, "paid")
}

func (s *PaymentService) ChargeCustomer(customerID string, amount int64) (*omise.Charge, error) {
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: amount,
Currency: "THB",
Customer: customerID,
}

if err := s.client.Do(charge, createCharge); err != nil {
return nil, err
}

return charge, nil
}

func (s *PaymentService) RefundCharge(chargeID string, amount *int64) (*omise.Refund, error) {
refund, refundOp := &omise.Refund{}, &operations.CreateRefund{
ChargeID: chargeID,
}

if amount != nil {
refundOp.Amount = *amount
}

if err := s.client.Do(refund, refundOp); err != nil {
return nil, err
}

return refund, nil
}

Concurrent Operations with Goroutinesโ€‹

Processing Multiple Chargesโ€‹

func processChargesConcurrently(
client *omise.Client,
tokens []string,
amount int64,
) ([]*omise.Charge, []error) {
type result struct {
charge *omise.Charge
err error
}

results := make(chan result, len(tokens))

for _, token := range tokens {
go func(t string) {
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: amount,
Currency: "THB",
Card: t,
}

err := client.Do(charge, createCharge)
results <- result{charge: charge, err: err}
}(token)
}

var charges []*omise.Charge
var errors []error

for i := 0; i < len(tokens); i++ {
res := <-results
if res.err != nil {
errors = append(errors, res.err)
} else {
charges = append(charges, res.charge)
}
}

return charges, errors
}

Worker Pool Patternโ€‹

type ChargeJob struct {
Token string
Amount int64
}

type ChargeWorkerPool struct {
client *omise.Client
workers int
}

func NewChargeWorkerPool(client *omise.Client, workers int) *ChargeWorkerPool {
return &ChargeWorkerPool{
client: client,
workers: workers,
}
}

func (p *ChargeWorkerPool) ProcessJobs(jobs []ChargeJob) []*omise.Charge {
jobsChan := make(chan ChargeJob, len(jobs))
resultsChan := make(chan *omise.Charge, len(jobs))

// Start workers
for i := 0; i < p.workers; i++ {
go p.worker(jobsChan, resultsChan)
}

// Send jobs
for _, job := range jobs {
jobsChan <- job
}
close(jobsChan)

// Collect results
var charges []*omise.Charge
for i := 0; i < len(jobs); i++ {
charge := <-resultsChan
if charge != nil {
charges = append(charges, charge)
}
}

return charges
}

func (p *ChargeWorkerPool) worker(jobs <-chan ChargeJob, results chan<- *omise.Charge) {
for job := range jobs {
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: job.Amount,
Currency: "THB",
Card: job.Token,
}

if err := p.client.Do(charge, createCharge); err != nil {
results <- nil
} else {
results <- charge
}
}
}

Best Practicesโ€‹

1. Use Context for Timeout Controlโ€‹

func createChargeWithTimeout(
client *omise.Client,
token string,
amount int64,
timeout time.Duration,
) (*omise.Charge, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: amount,
Currency: "THB",
Card: token,
}

if err := client.DoWithContext(ctx, charge, createCharge); err != nil {
return nil, err
}

return charge, nil
}

2. Implement Idempotencyโ€‹

import "github.com/google/uuid"

func createIdempotentCharge(
client *omise.Client,
token string,
amount int64,
orderID string,
) (*omise.Charge, error) {
idempotencyKey := fmt.Sprintf("order-%s-%s", orderID, uuid.New().String())

charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: amount,
Currency: "THB",
Card: token,
Options: operations.Options{
IdempotencyKey: idempotencyKey,
},
}

if err := client.Do(charge, createCharge); err != nil {
return nil, err
}

return charge, nil
}

3. Use Structured Loggingโ€‹

import "log/slog"

type PaymentLogger struct {
logger *slog.Logger
}

func NewPaymentLogger() *PaymentLogger {
return &PaymentLogger{
logger: slog.Default(),
}
}

func (l *PaymentLogger) LogCharge(charge *omise.Charge, err error) {
if err != nil {
l.logger.Error("Charge failed",
slog.String("error", err.Error()),
)
return
}

l.logger.Info("Charge created",
slog.String("charge_id", charge.ID),
slog.Int64("amount", charge.Amount),
slog.String("currency", charge.Currency),
slog.Bool("paid", charge.Paid),
)
}

4. Use Custom Types for Moneyโ€‹

type Money struct {
Amount int64
Currency string
}

func NewMoney(amount float64, currency string) Money {
return Money{
Amount: int64(amount * 100),
Currency: currency,
}
}

func (m Money) ToFloat() float64 {
return float64(m.Amount) / 100
}

// Usage
money := NewMoney(1000.00, "THB")
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: money.Amount,
Currency: money.Currency,
Card: token,
}

5. Implement Retry Logicโ€‹

import "time"

func retryOperation(
fn func() error,
maxRetries int,
baseDelay time.Duration,
) error {
for attempt := 0; attempt < maxRetries; attempt++ {
if err := fn(); err != nil {
if attempt == maxRetries-1 {
return err
}

delay := baseDelay * time.Duration(1<<uint(attempt))
time.Sleep(delay)
continue
}
return nil
}
return nil
}

// Usage
err := retryOperation(func() error {
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: 100000,
Currency: "THB",
Card: token,
}
return client.Do(charge, createCharge)
}, 3, time.Second)

6. Use Interfaces for Testingโ€‹

type ChargeCreator interface {
CreateCharge(token string, amount int64) (*omise.Charge, error)
}

type OmiseChargeCreator struct {
client *omise.Client
}

func (o *OmiseChargeCreator) CreateCharge(token string, amount int64) (*omise.Charge, error) {
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: amount,
Currency: "THB",
Card: token,
}

if err := o.client.Do(charge, createCharge); err != nil {
return nil, err
}

return charge, nil
}

// Mock for testing
type MockChargeCreator struct {
MockCharge *omise.Charge
MockError error
}

func (m *MockChargeCreator) CreateCharge(token string, amount int64) (*omise.Charge, error) {
return m.MockCharge, m.MockError
}

Testingโ€‹

Unit Testing with Mocksโ€‹

package payment

import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/omise/omise-go/v2"
)

func TestCreateCharge(t *testing.T) {
// Create mock
mockCreator := &MockChargeCreator{
MockCharge: &omise.Charge{
ID: "chrg_test_123",
Amount: 100000,
Currency: "THB",
Paid: true,
},
MockError: nil,
}

// Test
charge, err := mockCreator.CreateCharge("tokn_test_123", 100000)

assert.NoError(t, err)
assert.NotNil(t, charge)
assert.Equal(t, "chrg_test_123", charge.ID)
assert.True(t, charge.Paid)
}

func TestCreateCharge_Error(t *testing.T) {
mockCreator := &MockChargeCreator{
MockCharge: nil,
MockError: errors.New("insufficient_fund"),
}

charge, err := mockCreator.CreateCharge("tokn_test_123", 100000)

assert.Error(t, err)
assert.Nil(t, charge)
assert.Contains(t, err.Error(), "insufficient_fund")
}

Integration Testingโ€‹

func TestCreateChargeIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test")
}

client, err := omise.NewClient(
"pkey_test_123456789",
"skey_test_123456789",
)
assert.NoError(t, err)

charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: 100000,
Currency: "THB",
Card: "tokn_test_5086xl7ddjbases4sq3i",
}

err = client.Do(charge, createCharge)
assert.NoError(t, err)
assert.NotEmpty(t, charge.ID)
assert.False(t, charge.Livemode)
}

Troubleshootingโ€‹

Connection Timeoutโ€‹

import "time"

client, _ := omise.NewClient("pkey", "skey")
client.SetTimeout(60 * time.Second)

Debug Loggingโ€‹

client, _ := omise.NewClient("pkey", "skey")
client.SetDebug(true) // Enable debug logging

Webhook Signature Verificationโ€‹

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)

func verifyWebhookSignature(payload []byte, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expectedSignature := hex.EncodeToString(mac.Sum(nil))

return hmac.Equal([]byte(signature), []byte(expectedSignature))
}

// HTTP Handler
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, _ := ioutil.ReadAll(r.Body)
signature := r.Header.Get("Omise-Signature")

if !verifyWebhookSignature(body, signature, os.Getenv("OMISE_WEBHOOK_SECRET")) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}

// Process webhook
w.WriteHeader(http.StatusOK)
}

FAQโ€‹

How do I handle webhooks?โ€‹

type WebhookEvent struct {
Key string `json:"key"`
Data map[string]interface{} `json:"data"`
}

func handleWebhook(client *omise.Client, event WebhookEvent) error {
switch event.Key {
case "charge.complete":
return handleChargeComplete(client, event.Data["id"].(string))
case "refund.create":
return handleRefundCreate(client, event.Data)
}
return nil
}

func handleChargeComplete(client *omise.Client, chargeID string) error {
charge, retrieve := &omise.Charge{}, &operations.RetrieveCharge{
ChargeID: chargeID,
}

if err := client.Do(charge, retrieve); err != nil {
return err
}

// Update payment status
return nil
}

How do I test without real charges?โ€‹

// Use test API keys
client, _ := omise.NewClient(
"pkey_test_123456789",
"skey_test_123456789",
)

// Use test tokens
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: 100000,
Currency: "THB",
Card: "tokn_test_5086xl7ddjbases4sq3i",
}

client.Do(charge, createCharge)
fmt.Println("Test mode:", !charge.Livemode)

How do I handle partial refunds?โ€‹

func refundCharge(client *omise.Client, chargeID string, amount *int64) (*omise.Refund, error) {
// Get charge to check refundable amount
charge, retrieve := &omise.Charge{}, &operations.RetrieveCharge{
ChargeID: chargeID,
}

if err := client.Do(charge, retrieve); err != nil {
return nil, err
}

refundable := charge.Amount - charge.Refunded

if amount != nil && *amount > refundable {
return nil, errors.New("refund amount exceeds refundable amount")
}

refund, refundOp := &omise.Refund{}, &operations.CreateRefund{
ChargeID: chargeID,
}

if amount != nil {
refundOp.Amount = *amount
}

if err := client.Do(refund, refundOp); err != nil {
return nil, err
}

return refund, nil
}

How do I save multiple cards for a customer?โ€‹

func addCardToCustomer(client *omise.Client, customerID, token string) (*omise.Customer, error) {
customer, updateOp := &omise.Customer{}, &operations.UpdateCustomer{
CustomerID: customerID,
Card: token,
}

if err := client.Do(customer, updateOp); err != nil {
return nil, err
}

return customer, nil
}

func listCustomerCards(client *omise.Client, customerID string) ([]*omise.Card, error) {
customer, retrieve := &omise.Customer{}, &operations.RetrieveCustomer{
CustomerID: customerID,
}

if err := client.Do(customer, retrieve); err != nil {
return nil, err
}

return customer.Cards.Data, nil
}

func chargeSpecificCard(
client *omise.Client,
customerID, cardID string,
amount int64,
) (*omise.Charge, error) {
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: amount,
Currency: "THB",
Customer: customerID,
Card: cardID,
}

if err := client.Do(charge, createCharge); err != nil {
return nil, err
}

return charge, nil
}

How do I implement subscription billing?โ€‹

type SubscriptionManager struct {
client *omise.Client
}

func NewSubscriptionManager(client *omise.Client) *SubscriptionManager {
return &SubscriptionManager{client: client}
}

func (s *SubscriptionManager) ChargeMonthly(customerID string, planAmount int64) (*omise.Charge, error) {
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: planAmount,
Currency: "THB",
Customer: customerID,
Description: fmt.Sprintf("Subscription %s", time.Now().Format("January 2006")),
}

if err := s.client.Do(charge, createCharge); err != nil {
return nil, err
}

return charge, nil
}

// With cron
import "github.com/robfig/cron/v3"

func scheduleSubscriptionCharges(manager *SubscriptionManager) {
c := cron.New()
c.AddFunc("0 0 1 * *", func() { // First day of every month
// Charge all subscriptions
})
c.Start()
}

Next Stepsโ€‹

Supportโ€‹

If you encounter issues with the Go library:

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