Go SDK for XorPay, aligned with the official Java demo flow and extended to full public API coverage.
- Zero dependencies — built entirely on the Go standard library (
net/http). - Strongly typed — request/response structs with validation for every endpoint.
- Secure by default — automatic signature generation and callback verification.
- Well tested — comprehensive offline, mock-based unit tests with race detection.
- Features
- Installation
- Quick Start
- Supported Payment Types
- Configuration
- Examples
- Notify Verification
- Error Handling
- Documentation
- Development
- License
| API | SDK Method | Endpoint |
|---|---|---|
| Native / JSAPI / Mini Program Pay | CreatePay |
POST /api/pay/{aid} |
| Cashier Pay | CreateCashier |
POST /api/cashier/{aid} |
| Barcode Pay | CreateBarcodePay |
POST /api/barcode_pay/{aid} |
| Query by AOID | QueryByAOID |
GET /api/query/{aoid} |
| Query by Order ID | QueryByOrderID |
GET /api/query2/{aid} |
| Refund | Refund |
POST /api/refund/{aoid} |
| Build OpenID URL | BuildOpenIDURL |
/api/openid/{aid} |
| Build QR URL | BuildQRURL |
/qr?data=... |
| Notify Verification | VerifyNotify |
— |
Requires Go 1.25 or later.
go get github.com/warjiang/xorpay-sdkpackage main
import (
"context"
"fmt"
"github.com/warjiang/xorpay-sdk"
)
func main() {
client, err := xorpay.NewClient(xorpay.ConfigFromEnv())
if err != nil {
panic(err)
}
resp, err := client.CreatePay(context.Background(), xorpay.PayRequest{
Name: "内容订阅一年期",
PayType: "native",
Price: "50.00",
OrderID: "demo-0001",
NotifyURL: "https://merchant.example.com/xorpay_notify",
})
if err != nil {
panic(err)
}
fmt.Println(resp.Status, resp.AOID, string(resp.Info))
}Or pass credentials explicitly:
client, err := xorpay.NewClient(xorpay.Config{
AppID: "mock_appid",
AppSecret: "mock_secret",
})PayType |
Description | SDK Method | Notes |
|---|---|---|---|
native |
Native / QR code payment | CreatePay |
Returns a payment URL or QR data |
cashier |
Cashier page redirect | CreateCashier |
Use for web checkout pages |
jsapi |
WeChat JSAPI | CreatePay |
Requires OpenID |
barcode |
Barcode / scan-to-pay | CreateBarcodePay |
Requires Barcode |
miniprogram |
WeChat Mini Program | CreatePay |
Set IsMini: true and provide AppID |
Load from environment variables:
cfg := xorpay.ConfigFromEnv()
client, err := xorpay.NewClient(cfg)Or build Config explicitly:
| Field | Required | Env Var Fallback | Description |
|---|---|---|---|
AppID |
yes | XORPAY_APP_ID |
Your XorPay aid |
AppSecret |
yes | XORPAY_APP_SECRET |
Your app secret |
BaseURL |
no | — | API base URL (default: https://xorpay.com) |
NotifyURL |
no | XORPAY_NOTIFY_URL |
Default notify URL for payment APIs |
ReturnURL |
no | XORPAY_RETURN_URL |
Default return URL for cashier / jsapi |
Client options:
client, err := xorpay.NewClient(cfg,
xorpay.WithBaseURL("https://xorpay.com"),
xorpay.WithHTTPClient(customHTTPClient),
xorpay.WithUserAgent("my-app/1.0"),
)Set environment variables first:
export XORPAY_APP_ID=mock_appid
export XORPAY_APP_SECRET=mock_secret
export XORPAY_NOTIFY_URL=https://merchant.example.com/xorpay_notify
export XORPAY_RETURN_URL=https://merchant.example.com/xorpay_returnRun standalone examples:
go run ./examples/pay_native
go run ./examples/pay_cashier
go run ./examples/pay_jsapi
go run ./examples/pay_barcode
go run ./examples/query
go run ./examples/refund
go run ./examples/notify_verify
go run ./examples/internalcfgA complete web demo with order state management is available under examples/gin_app:
cd examples/gin_app
go run .Environment variables for the Gin demo:
| Variable | Required | Default |
|---|---|---|
XORPAY_APP_ID |
yes | — |
XORPAY_APP_SECRET |
yes | — |
XORPAY_NOTIFY_URL |
yes | — |
XORPAY_RETURN_URL |
no | — |
XORPAY_BASE_URL |
no | https://xorpay.com |
GIN_ADDR |
no | :8080 |
- Java:
./demos/java-demo.zip - Python:
./demos/native.py.zip - Node.js:
./demos/native.js.zip - H5:
./demos/h5.zip - H5 Cashier:
./demos/h5-cashier.zip
Always verify the callback signature before processing business logic:
func notifyHandler(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
payload := xorpay.NotifyPayloadFromValues(r.Form)
if err := client.VerifyNotify(payload); err != nil {
http.Error(w, "bad sign", http.StatusBadRequest)
return
}
// Production checklist:
// 1. Query the database by payload.OrderID (or payload.AOID).
// 2. If the order is already marked as paid, return "ok" immediately (idempotency).
// 3. Otherwise, update the order status in a transaction and return "ok" only on success.
// 4. Do NOT process business logic before signature verification.
_, _ = w.Write([]byte("ok"))
}Signature rules used by the SDK (consistent with XorPay official docs):
| API | Signature Formula |
|---|---|
| Pay / Cashier | name + pay_type + price + order_id + notify_url + app_secret |
| Barcode Pay | name + pay_type + price + order_id + notify_url + barcode + app_secret |
| Query by Order ID | order_id + app_secret |
| Refund | price + app_secret |
| Notify Verify | aoid + order_id + pay_price + pay_time + app_secret |
All signatures are lowercase MD5 over plain concatenated values.
Business-level non-success responses return *xorpay.APIError:
resp, err := client.CreatePay(ctx, req)
if err != nil {
if xorpay.IsStatus(err, "sign_error") {
// handle sign mismatch
}
var apiErr *xorpay.APIError
if errors.As(err, &apiErr) {
fmt.Println(apiErr.Endpoint, apiErr.Status, apiErr.Message)
}
}HTTP errors (status >= 400) are also wrapped as *APIError with HTTPStatus and RawBody populated.
- Getting Started — client setup, typical flow, and best practices
- API Reference — detailed method signatures and field descriptions
- Gin Integration — full web integration guide
# Run tests with race detection and coverage
go test -race -coverprofile=coverage.out ./...
# Static analysis
go vet ./...
# Format code
gofmt -w .CI is configured via GitHub Actions and runs on every push to main and on pull requests.