メインコンテンツへスキップ

Goライブラリ (omise-go)

omise-go ライブラリは、ゴルーチン、チャネル、コンテキスト対応操作、および最新のGoのベストプラクティスをサポートする、Omise APIへの慣用的なGoインターフェースを提供します。

インストール

go getを使用

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

go.modを使用

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

要件

  • Go 1.16以上(Go 1.20+を含む)
  • 依存関係管理用のGoモジュール

クイックスタート

基本的な構成

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)
}
}

環境変数を使用

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"),
)
}

構成構造体を使用

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
}

環境変数

# 開発/テスト
export OMISE_SECRET_KEY=skey_test_123456789
export OMISE_PUBLIC_KEY=pkey_test_123456789

# 本番環境
# export OMISE_SECRET_KEY=skey_live_123456789
# export OMISE_PUBLIC_KEY=pkey_live_123456789

一般的な操作

チャージの作成

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サタン
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("チャージ作成失敗: %w", err)
}

if charge.Paid {
fmt.Printf("チャージ成功: %s\n", charge.ID)
} else {
fmt.Printf("チャージ失敗: %s\n", charge.FailureMessage)
}

return charge, nil
}

コンテキストを使用

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
}

// タイムアウト付きの使用方法
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)
}
}

3D セキュアで

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("チャージ失敗: %s", charge.FailureMessage)
}

チャージの取得

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("金額: %d\n", charge.Amount)
fmt.Printf("通貨: %s\n", charge.Currency)
fmt.Printf("ステータス: %s\n", charge.Status)
fmt.Printf("支払い済み: %t\n", charge.Paid)

return charge, nil
}

チャージのリスト表示

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
}

// 日付フィルタリング付き
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
}

// 支払い済みチャージをフィルタ
var paidCharges []*omise.Charge
for _, charge := range chargeList.Data {
if charge.Paid {
paidCharges = append(paidCharges, charge)
}
}

return paidCharges, nil
}

顧客の作成

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("顧客が作成されました: %s\n", customer.ID)
return customer, nil
}

顧客へのカード保存

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("カードが保存されました: %s\n", customer.DefaultCard)
return customer, nil
}

// カード付きで顧客を作成
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
}

顧客カードのリスト表示

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 末尾 %s\n", card.Brand, card.LastDigits)
fmt.Printf("有効期限: %d/%d\n", card.ExpirationMonth, card.ExpirationYear)
}

return customer.Cards.Data, nil
}

払い戻しの作成

// 全額返金
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
}

// 部分払い戻し
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("払い戻し %s: %d %s\n", refund.ID, refund.Amount, refund.Currency)
return refund, nil
}

転送の作成

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("転送 %s: %d\n", transfer.ID, transfer.Amount)
return transfer, nil
}

代替決済方法

ソースの作成

// PromptPay 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コードURL: %s\n", source.ScannableCode.Image.DownloadURI)

// ソースでチャージを作成
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
}

インターネットバンキング

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
}

// charge.AuthorizeURIにリダイレクト
return charge, nil
}

インストール

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
}

エラーハンドリング

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("無効なAPIキー")
case "invalid_card":
return nil, errors.New("カードが拒否されました")
case "insufficient_fund":
return nil, errors.New("残高不足")
default:
return nil, fmt.Errorf("omiseエラー: %s", omiseErr.Message)
}
}
return nil, fmt.Errorf("リクエスト失敗: %w", err)
}

return charge, nil
}

カスタムエラーハンドラー

type PaymentErrorHandler struct {
errorMessages map[string]string
}

func NewPaymentErrorHandler() *PaymentErrorHandler {
return &PaymentErrorHandler{
errorMessages: map[string]string{
"insufficient_fund": "カードの残高不足",
"stolen_or_lost_card": "カードが盗難または紛失として報告されています",
"invalid_security_code": "無効なCVVコード",
"payment_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サーバー統合

ハンドラーの例

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, "無効なリクエスト", http.StatusBadRequest)
return
}

order, err := h.paymentService.GetOrder(req.OrderID)
if err != nil {
http.Error(w, "注文が見つかりません", 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, "支払いが失敗しました", http.StatusInternalServerError)
return
}

// 支払いを保存
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, "支払いの保存に失敗しました", 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, "支払い検証が失敗しました", http.StatusInternalServerError)
return
}

if err := h.paymentService.UpdatePaymentStatus(charge.ID, charge.Status, charge.Paid); err != nil {
http.Error(w, "支払いの更新に失敗しました", 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)
}
}

サービス層

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
}

ゴルーチンによる並行操作

複数のチャージの処理

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
}

ワーカープールパターン

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))

// ワーカーを開始
for i := 0; i < p.workers; i++ {
go p.worker(jobsChan, resultsChan)
}

// ジョブを送信
for _, job := range jobs {
jobsChan <- job
}
close(jobsChan)

// 結果を収集
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
}
}
}

ベストプラクティス

1. タイムアウト制御にコンテキストを使用

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. べき等性を実装

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. 構造化ログを使用

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("チャージ失敗",
slog.String("error", err.Error()),
)
return
}

l.logger.Info("チャージ作成",
slog.String("charge_id", charge.ID),
slog.Int64("amount", charge.Amount),
slog.String("currency", charge.Currency),
slog.Bool("paid", charge.Paid),
)
}

4. 金額用のカスタムタイプを使用

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
}

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

5. リトライロジックを実装

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
}

// 使用方法
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. テスト用のインターフェースを使用

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
}

// テスト用のモック
type MockChargeCreator struct {
MockCharge *omise.Charge
MockError error
}

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

テスト

モック付きのユニットテスト

package payment

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

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

// テスト
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")
}

統合テスト

func TestCreateChargeIntegration(t *testing.T) {
if testing.Short() {
t.Skip("統合テストをスキップ")
}

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)
}

トラブルシューティング

接続タイムアウト

import "time"

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

デバッグログ

client, _ := omise.NewClient("pkey", "skey")
client.SetDebug(true) // デバッグログを有効化

ウェブフック署名検証

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ハンドラー
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, "無効な署名", http.StatusUnauthorized)
return
}

// Webhookを処理
w.WriteHeader(http.StatusOK)
}

よくある質問

Webhookを処理するにはどうすればよいですか?

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
}

// 支払いステータスを更新
return nil
}

実際のチャージなしでテストするにはどうすればよいですか?

// テストAPIキーを使用
client, _ := omise.NewClient(
"pkey_test_123456789",
"skey_test_123456789",
)

// テストトークンを使用
charge, createCharge := &omise.Charge{}, &operations.CreateCharge{
Amount: 100000,
Currency: "THB",
Card: "tokn_test_5086xl7ddjbases4sq3i",
}

client.Do(charge, createCharge)
fmt.Println("テストモード:", !charge.Livemode)

部分払い戻しを処理するにはどうすればよいですか?

func refundCharge(client *omise.Client, chargeID string, amount *int64) (*omise.Refund, error) {
// 払い戻し可能額を確認するためにチャージを取得
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, 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
}

顧客に複数のカードを保存するにはどうすればよいですか?

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
}

サブスクリプション請求を実装するにはどうすればよいですか?

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("サブスクリプション %s", time.Now().Format("January 2006")),
}

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

return charge, nil
}

// cronと一緒に使用
import "github.com/robfig/cron/v3"

func scheduleSubscriptionCharges(manager *SubscriptionManager) {
c := cron.New()
c.AddFunc("0 0 1 * *", func() { // 毎月1日
// すべてのサブスクリプションにチャージ
})
c.Start()
}

関連リソース

次のステップ

サポート

Goライブラリで問題が発生した場合:

  1. GitHub Issuesを確認してください
  2. APIドキュメントを確認してください
  3. support@omise.coに以下の情報をご連絡ください:
    • Goバージョン
    • omise-goライブラリバージョン
    • エラーメッセージとスタックトレース
    • 再現手順