-
Notifications
You must be signed in to change notification settings - Fork 0
Errors
Every failure is a *payvand.Error wrapping a sentinel, so you can match
broadly and still read the provider's own code.
type Error struct {
Gateway Name // "mellat"
Op string // "purchase", "verify", "refund", "inquiry", "callback", "new"
Code string // the provider's status code, when it published one
Message string // the provider's message, in English where Payvand knows it
Err error // the sentinel it unwraps to
}| Sentinel | Meaning | Usual reaction |
|---|---|---|
ErrNotSupported |
the provider has no such API | check Capabilities() first |
ErrGatewayNotRegistered |
unknown gateway name | fix the configuration; fail at start-up |
ErrInvalidConfig |
missing or malformed credentials | fix the terminal; this only fires in New
|
ErrInvalidRequest |
the request cannot be sent | fix the caller: zero amount, missing order id, non-numeric id |
ErrPaymentFailed |
the provider rejected the operation | read Code and Message; usually final |
ErrPaymentCanceled |
the payer aborted | mark the order canceled |
ErrAlreadyVerified |
the transaction was verified before | treat as success, do not double-ship |
ErrAmountMismatch |
the settled amount differs | stop and reconcile by hand |
ErrUnexpectedResponse |
the body could not be understood | provider outage or a protocol change; retry, then alert |
verified, err := gw.Verify(ctx, req)
switch {
case errors.Is(err, payvand.ErrAlreadyVerified):
// fine
case errors.Is(err, payvand.ErrAmountMismatch):
alert("amount mismatch on order " + order.ID)
case errors.Is(err, payvand.ErrPaymentFailed):
var e *payvand.Error
errors.As(err, &e)
log.Printf("gateway=%s op=%s code=%s message=%s", e.Gateway, e.Op, e.Code, e.Message)
case err != nil:
// transport, timeout, unexpected body
}Gateways whose provider publishes a code table expose it, translated into English:
mellat.Message("421") // "the ip address is invalid"
irankish.Message("51") // "insufficient funds"
saman.Message(-5) // "the transaction was already verified"
nextpay.Message(-49) // "the transaction was already verified"
bitpay.Message(-4) // "the api key is invalid"The raw code is always in Error.Code, so you can log it and look it up in the
provider's own documentation.
Provider messages are written for merchants, not for payers, and some leak internal detail. A safe mapping:
| Situation | Message to the payer |
|---|---|
ErrPaymentCanceled, Succeeded == false
|
"You canceled the payment." |
ErrPaymentFailed with a card-side code (insufficient funds, wrong PIN, expired card) |
"Your bank declined the payment. Please try another card." |
ErrAmountMismatch, ErrUnexpectedResponse, transport errors |
"We could not confirm your payment. If money left your account it will be returned within 72 hours." |
| Anything else | a generic failure, plus the order id for support |
Keep Error.Code, Error.Message and the Raw body in your own logs — that
is what the PSP's support desk will ask for.
WithRetry(n, backoff) retries network errors and 5xx responses only. It never
retries a 4xx, because those are business rejections.
A timeout on Purchase is harmless: no token, no payment. A timeout on
Verify is not — the provider may have settled the transaction anyway. Treat
it as unknown, and resolve it with Inquiry rather than by assuming failure.
Next: Testing · Callbacks and Verification
Payvand · MIT licensed · built on the Go standard library alone · report an issue