-
Notifications
You must be signed in to change notification settings - Fork 0
Migration Guide
Most Iranian Go services grew their own IPG layer with a GetToken /
Confirm / Reverse interface and a switch over gateway constants. Moving
that onto Payvand is mechanical, and it can be done one gateway at a time.
| Your old code | Payvand |
|---|---|
NewIPG(cfg, db, gateway, terminalInfo) |
pv.Gateway(name, payvand.Config{…}) |
GetToken(ctx, GetTokenReq{…}) |
Purchase(ctx, payvand.PurchaseRequest{…}) |
res.PaymentToken |
res.Token |
res.URL |
res.Redirect (.String(), .Send(w, r)) |
Confirm(ctx, ConfirmReq{…}) |
Verify(ctx, payvand.VerifyRequest{…}) |
res.FinalReferenceNumber |
res.ReferenceNumber |
res.CardNumber |
res.CardNumber |
Reverse(ctx, ReverseReq{…}) |
Refund(ctx, payvand.RefundRequest{…}) |
| hand-parsed callback query/form |
ParseCallback(r) + Callback.VerifyRequest(amount)
|
TerminalInfo{Username, Password, TerminalID, MerchantID, MerchantKey, IBAN} |
payvand.Config — same fields, same meaning |
amount int64 in Rial |
payvand.Rial(amount) |
switch gateway { case ZIBAL: … } |
the registry: the name is data |
| "this gateway cannot reverse" scattered in the code | gw.Capabilities().Refund |
Keep every call site untouched by wrapping Payvand in your existing interface:
type ipgAdapter struct{ gw payvand.Gateway }
func (a ipgAdapter) GetToken(ctx context.Context, req dto.GetTokenReq) (dto.GetTokenRes, error) {
res, err := a.gw.Purchase(ctx, payvand.PurchaseRequest{
Amount: payvand.Rial(req.Amount),
OrderID: req.OrderID,
CallbackURL: req.CallbackUrl,
Mobile: req.Mobile,
NationalID: req.NationalId,
})
if err != nil {
return dto.GetTokenRes{}, err
}
return dto.GetTokenRes{PaymentToken: res.Token, URL: res.Redirect.String()}, nil
}
func (a ipgAdapter) Confirm(ctx context.Context, req dto.ConfirmReq) (dto.ConfirmRes, error) {
res, err := a.gw.Verify(ctx, payvand.VerifyRequest{
Token: req.PaymentToken,
Amount: payvand.Rial(req.Amount),
ReferenceNumber: req.ReferenceNumber,
TraceNumber: req.TraceNumber,
CardNumber: req.CardNumber,
})
if err != nil {
return dto.ConfirmRes{}, err
}
return dto.ConfirmRes{
Amount: res.Amount.Rial(),
FinalReferenceNumber: res.ReferenceNumber,
CardNumber: res.CardNumber,
}, nil
}
func (a ipgAdapter) Reverse(ctx context.Context, req dto.ReverseReq) (dto.ReverseRes, error) {
_, err := a.gw.Refund(ctx, payvand.RefundRequest{
Token: req.PaymentToken,
ReferenceNumber: req.FinalReferenceNumber,
Amount: payvand.Rial(req.Amount),
})
return dto.ReverseRes{}, err
}Your factory becomes one line:
func NewIPG(gateway string, terminal TerminalInfo) (IPG, error) {
gw, err := payvand.New(payvand.Name(gateway), payvand.Config{
Username: terminal.Username,
Password: terminal.Password,
TerminalID: terminal.TerminalID,
MerchantID: terminal.MerchantID,
MerchantKey: terminal.MerchantKey,
IBAN: terminal.IBAN,
})
if err != nil {
return nil, err
}
return ipgAdapter{gw: gw}, nil
}At this point the old per-gateway files can be deleted.
The hand-written callback parsing is usually the messiest part, and it is the one Payvand shortens the most:
cb, err := gw.ParseCallback(r)
if err != nil || !cb.Succeeded {
return errPaymentCanceled
}
order := orders.ByToken(cb.Token)
verified, err := gw.Verify(ctx, cb.VerifyRequest(order.Amount))Note what disappears: per-gateway field names, casing fallbacks, and the manual copying of reference and trace numbers into the confirm request.
Once the callback path uses Payvand types, change the call sites to
payvand.Gateway directly and delete the adapter. Order matters less than
finishing: the adapter can live for a release or two without harm.
| Change | Why it matters |
|---|---|
| Amounts are typed |
payvand.Rial(x) vs payvand.Toman(x); the gateway converts, so double conversions in your own code must go |
Credentials are validated in New
|
a broken terminal now fails at start-up instead of at payment time |
Settlement is inside Verify
|
if you called Mellat's settle or AsanPardakht's settlement yourself, remove it |
Unsupported operations return ErrNotSupported
|
replace "this gateway does not support reverse" branches with Capabilities().Refund
|
Repeated verification returns ErrAlreadyVerified
|
treat it as success rather than as a failure |
| Redirects can be POST forms | replace a bare http.Redirect with purchase.Redirect.Send(w, r)
|
| Pasargad needs the invoice date | store PurchaseResponse.Extra[pasargad.InvoiceDateKey] with the order |
- Point staging at
payvand.Virtualand run your existing payment tests. - Re-run them against a fake provider with
payvand.WithBaseURL, replaying the payloads you captured from the real gateway. - Move one low-traffic terminal to production and compare reference numbers with the provider panel for a day.
- Migrate the rest.
Payvand · MIT licensed · built on the Go standard library alone · report an issue