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()
}
Related Resourcesโ
- Omise API Documentation
- omise-go GitHub Repository
- Go Package Documentation
- Omise Dashboard
- Webhooks Guide
- Testing Guide
Next Stepsโ
Supportโ
If you encounter issues with the Go library:
- Check the GitHub Issues
- Review the API documentation
- Contact support@omise.co with:
- Go version
- omise-go library version
- Error message and stack trace
- Steps to reproduce