-
Notifications
You must be signed in to change notification settings - Fork 0
Testing
Payvand is built to be testable without a merchant account: every gateway
honours WithBaseURL, and one of them talks to nobody at all.
payvand.Virtual runs the whole purchase → callback → verify → refund cycle in
memory.
gw, _ := payvand.New(payvand.Virtual, payvand.Config{})
purchase, _ := gw.Purchase(ctx, payvand.PurchaseRequest{
Amount: payvand.Toman(15_000),
OrderID: "1001",
CallbackURL: "https://shop.example/callback",
})
// The payer "comes back": the redirect the gateway produced is a valid callback.
r := httptest.NewRequest(http.MethodGet, purchase.Redirect.String(), nil)
cb, _ := gw.ParseCallback(r)
verified, err := gw.Verify(ctx, cb.VerifyRequest(payvand.Toman(15_000)))Failure paths are options, so your recovery code gets exercised too:
payvand.New(payvand.Virtual, cfg, virtual.WithDecline(true)) // payer declined
payvand.New(payvand.Virtual, cfg, virtual.WithFailingVerify(true)) // verification fails
virtual.WithRedirectURL("http://localhost:8080/fake-bank") // your own fake bank pagePoint your staging configuration at virtual and the rest of the application
does not change.
For gateway specific behaviour, serve the provider's own payloads from an
httptest.Server and redirect the gateway to it:
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/v1/request":
_, _ = w.Write([]byte(`{"result":100,"trackId":3355}`))
case "/v1/verify":
_, _ = w.Write([]byte(`{"result":100,"amount":150000,"refNumber":"9988"}`))
}
}))
defer server.Close()
gw, _ := payvand.New(payvand.Zibal, cfg, payvand.WithBaseURL(server.URL))WithBaseURL replaces both the API host and the payment page host, so you can
assert on the redirect URL as well.
| Case | Why |
|---|---|
| a repeated callback | payers refresh; the handler must be idempotent |
ErrAlreadyVerified |
a refreshed page must not turn a paid order into a failure |
| a callback with an unknown token | someone probing your endpoint |
| verification failure after a successful callback |
Succeeded is only a hint |
ErrAmountMismatch |
the alert path is the one you never want to discover in production |
a timeout during Verify
|
the payment may still have settled; the recovery job must handle it |
Every gateway has a test package driven by internal/testutil, which starts a
fake provider and fails the test on any unexpected path — a wrong endpoint
becomes an immediate, readable failure.
make test # go test -race ./...
make cover # coverage summary
make lint # gofmt check + go vet
make ci # everything the pipeline runsNo network, no credentials, no build tags: go test ./... works on a fresh
clone.
Some providers offer a real sandbox, reachable with payvand.WithSandbox(true):
| Provider | What sandbox mode does |
|---|---|
| Zarinpal | switches to sandbox.zarinpal.com
|
| Zibal | replaces the merchant id with the literal zibal
|
| Pay.ir | replaces the API key with test
|
| IDPay | sends the X-SANDBOX: 1 header |
| BitPay.ir | uses the public demo API key |
For the bank acquirers there is no public sandbox: you need a test terminal from the acquirer, and until then the virtual gateway plus a fake server is the honest substitute.
Next: Migration Guide · Extending
Payvand · MIT licensed · built on the Go standard library alone · report an issue