-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
go get github.com/amiranmanesh/payvandGo 1.26 or newer. There is nothing else to install: Payvand has no dependencies.
payvand.Init holds the settings shared by your whole application. Build it at
start-up and keep it.
pv := payvand.Init(
payvand.WithTimeout(20*time.Second),
payvand.WithUserAgent("shop/1.4"),
payvand.WithLogger(payvand.SlogLogger{Logger: slog.Default()}),
)gw, err := pv.Gateway(payvand.Zarinpal, payvand.Config{
MerchantKey: os.Getenv("ZARINPAL_MERCHANT_ID"),
})
if err != nil {
log.Fatal(err) // credentials are validated here, not at payment time
}The first argument is data. It can come from a configuration file, an environment variable or the merchant's row in your database — that is the whole point of the registry. See Configuration for what each provider needs.
Gateways are safe for concurrent use; build one per terminal and share it.
purchase, err := gw.Purchase(ctx, payvand.PurchaseRequest{
Amount: payvand.Toman(15_000),
OrderID: order.ID, // your unique id; several banks require it to be numeric
CallbackURL: "https://shop.example/payments/callback",
Description: "Wallet top-up",
Mobile: "09120000000", // lets the bank page offer saved cards
})
if err != nil {
return err
}
order.PaymentToken = purchase.Token
order.Save() // persist BEFORE redirectingpurchase.Redirect.Send(w, r)That single line covers both worlds: a 303 redirect for GET gateways, and an auto-submitting HTML form for the ones that require a POST (Mellat, Sepehr, Iran Kish, AsanPardakht). If you render the page yourself:
purchase.Redirect.String() // full URL with parameters appended
purchase.Redirect.IsPost() // whether a form is required
purchase.Redirect.HTML() // the auto-submitting form as a stringfunc callbackHandler(w http.ResponseWriter, r *http.Request) {
cb, err := gw.ParseCallback(r)
if err != nil {
http.Error(w, "unreadable callback", http.StatusBadRequest)
return
}
if !cb.Succeeded {
orders.MarkCanceled(cb.Token)
http.Error(w, "the payer canceled the payment", http.StatusPaymentRequired)
return
}
order := orders.ByToken(cb.Token)
verified, err := gw.Verify(r.Context(), cb.VerifyRequest(order.Amount))
switch {
case errors.Is(err, payvand.ErrAlreadyVerified):
// a refreshed callback page: the order is already paid
case err != nil:
orders.MarkFailed(order.ID, err)
http.Error(w, "the payment could not be verified", http.StatusPaymentRequired)
return
}
orders.MarkPaid(order.ID, verified.ReferenceNumber, verified.CardNumber)
}Verify is where the multi-step providers are hidden: Mellat verifies and
settles, AsanPardakht reads the transaction then verifies then settles, Vandar
reads the transaction then verifies, Pasargad checks then verifies. You call
one method.
if gw.Capabilities().Refund {
_, err := gw.Refund(ctx, payvand.RefundRequest{
Token: order.PaymentToken,
OrderID: order.ID,
ReferenceNumber: order.ReferenceNumber,
Amount: order.Amount,
})
}
if gw.Capabilities().Inquiry {
state, err := gw.Inquiry(ctx, payvand.InquiryRequest{
Token: order.PaymentToken,
OrderID: order.ID,
})
// state.Status: pending, paid, verified, failed, canceled, refunded
}| Mistake | Why it hurts |
|---|---|
Trusting cb.Succeeded and skipping Verify
|
the bank reverses the transaction and the payer gets the money back |
| Taking the amount from the callback | a payer can pay 1,000 Rial for a 1,000,000 Rial order |
| Redirecting before persisting the token | you cannot match the callback to an order |
Ignoring ErrAlreadyVerified
|
a refreshed callback page turns a paid order into a failed one |
| Assuming a unit | some providers take Rial, PayPing takes Toman; use payvand.Rial / payvand.Toman and let the gateway convert |
A non-numeric OrderID
|
Sadad, TOP, Mellat and Sepehr reject it |
git clone https://github.com/amiranmanesh/payvand
cd payvand
go run ./examples/basic # a full cycle, offline
go run ./examples/multigateway # the capability table, and switching providers
go run ./examples/webshop # a two-handler shop on :8080Payvand · MIT licensed · built on the Go standard library alone · report an issue