Documentation
¶
Index ¶
- Constants
- Variables
- func CallbackValues(r *http.Request) (map[string]string, error)
- func FirstValue(values map[string]string, keys ...string) string
- func IsRegistered(name Name) bool
- func Register(name Name, factory Factory)
- type Callback
- type Capabilities
- type Client
- type Config
- type Currency
- type Doer
- type Error
- type Factory
- type Gateway
- type InquiryRequest
- type InquiryResponse
- type Logger
- type Money
- type Name
- type NopLogger
- type Option
- func WithBaseURL(baseURL string) Option
- func WithHTTPClient(client Doer) Option
- func WithHeader(key, value string) Option
- func WithLogger(l Logger) Option
- func WithRetry(maxAttempts int, backoff time.Duration) Option
- func WithSandbox(enabled bool) Option
- func WithSkipTLSVerify(skip bool) Option
- func WithTimeout(d time.Duration) Option
- func WithUserAgent(ua string) Option
- type Options
- type PurchaseRequest
- type PurchaseResponse
- type Redirect
- type RefundRequest
- type RefundResponse
- type RetryPolicy
- type SlogLogger
- type Status
- type Unsupported
- type VerifyRequest
- type VerifyResponse
Constants ¶
const DefaultTimeout = 30 * time.Second
DefaultTimeout is the per-request timeout used when the caller does not set one. Iranian PSP endpoints are slow, hence the generous default.
const DefaultUserAgent = "payvand-go/1.2"
DefaultUserAgent is sent with every request unless overridden. It carries the minor version, so a provider reading its access logs can tell which verification behaviour a merchant is running.
Variables ¶
var ( // ErrNotSupported is returned by an operation the gateway does not // implement, e.g. Refund on Zarinpal. ErrNotSupported = errors.New("payvand: operation not supported by this gateway") // ErrGatewayNotRegistered is returned by [New] for an unknown gateway name. ErrGatewayNotRegistered = errors.New("payvand: gateway is not registered") // ErrInvalidConfig is returned when mandatory credentials are missing. ErrInvalidConfig = errors.New("payvand: invalid gateway configuration") // ErrInvalidRequest is returned when the caller supplied an unusable // request (zero amount, missing callback URL, malformed order id, ...). ErrInvalidRequest = errors.New("payvand: invalid request") // ErrPaymentFailed is returned when the gateway rejected the payment. ErrPaymentFailed = errors.New("payvand: payment failed") // ErrPaymentCanceled is returned when the payer aborted the payment. ErrPaymentCanceled = errors.New("payvand: payment canceled by payer") // ErrAlreadyVerified is returned when the transaction was verified before. ErrAlreadyVerified = errors.New("payvand: payment already verified") // ErrVerificationPending is returned when the provider accepted the // verification but has not settled it yet. The payment is neither failed // nor confirmed: call Verify again instead of asking the payer to pay a // second time. ErrVerificationPending = errors.New("payvand: verification still in progress") // ErrAmountMismatch is returned when the verified amount differs from the // amount the caller asked to verify. ErrAmountMismatch = errors.New("payvand: verified amount does not match requested amount") // ErrUnexpectedResponse is returned when the gateway answered with a body // that could not be understood. ErrUnexpectedResponse = errors.New("payvand: unexpected gateway response") )
Sentinel errors returned by every gateway implementation. Compare them with errors.Is; gateway specific detail is carried by Error, which wraps one of these.
Functions ¶
func CallbackValues ¶
CallbackValues merges the query string and the form body of an incoming callback into a flat map. It is the helper every gateway uses in Gateway.ParseCallback; the body is only parsed for methods that carry one.
func FirstValue ¶
FirstValue returns the first non-empty value among the given keys, which keeps gateways tolerant to the casing drift of the Iranian PSP callbacks.
func IsRegistered ¶
IsRegistered reports whether a gateway is linked into the binary.
Types ¶
type Callback ¶
type Callback struct {
// Gateway is the gateway that produced the callback.
Gateway Name
// Succeeded reports whether the bank says the payer completed the payment.
// It is a hint only: the payment is not final until [Gateway.Verify]
// returns without error.
Succeeded bool
// Token is the payment token, matching [PurchaseResponse.Token].
Token string
// OrderID is the merchant side order identifier, when the bank echoes it.
OrderID string
// ReferenceNumber is the bank reference number (RRN), when present.
ReferenceNumber string
// TraceNumber is the system trace audit number, when present.
TraceNumber string
// CardNumber is the masked PAN, when present.
CardNumber string
// Amount is the amount reported by the bank, when present.
Amount Money
// Code is the raw status/result code sent by the bank.
Code string
// Message is the raw message sent by the bank.
Message string
// Values holds every parameter of the callback (query and form merged),
// so provider specific fields stay reachable.
Values map[string]string
}
Callback is the provider independent view of the request the bank sends the payer back with. Feed it into a VerifyRequest to settle the payment.
func (Callback) VerifyRequest ¶
func (c Callback) VerifyRequest(amount Money) VerifyRequest
VerifyRequest builds the verification request matching this callback. The caller must still set the amount from its own records, because trusting the amount reported by the browser is never safe.
type Capabilities ¶
type Capabilities struct {
// Verify reports whether the gateway supports payment verification.
// Every real gateway does; the field exists for completeness.
Verify bool
// Refund reports whether the gateway can reverse a payment.
Refund bool
// Inquiry reports whether the gateway can be asked for a transaction
// status out of band.
Inquiry bool
// Callback reports whether the gateway calls back into the merchant
// server (false for in-app flows such as TOP).
Callback bool
// RedirectMethod is the HTTP method the payer's browser must use to reach
// the bank page: http.MethodGet or http.MethodPost.
RedirectMethod string
// Multiplexing reports whether the gateway can split a single payment
// between several IBANs (settlement sharing).
Multiplexing bool
// Currencies lists the units the provider's own API accepts. Payvand
// converts to the first entry automatically.
Currencies []Currency
}
Capabilities describes what a gateway can do, so callers can build a generic UI or admin panel without special-casing providers.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the initialised entry point of the package: it remembers the options shared by the whole application (HTTP client, timeout, logger, retries) so that every gateway created from it inherits them.
pv := payvand.Init(payvand.WithTimeout(20 * time.Second))
gw, err := pv.Gateway(payvand.Zarinpal, payvand.Config{MerchantKey: key})
func (*Client) Gateway ¶
Gateway builds the named gateway with the client defaults, overridden by the options given here.
func (*Client) MustGateway ¶
MustGateway is Client.Gateway for the wiring phase of a program: it panics instead of returning an error, which is what you want when a misconfigured terminal must stop the process from starting.
type Config ¶
type Config struct {
// MerchantID is the merchant/business identifier (Sadad MerchantId,
// Vandar business id, Pasargad MerchantCode, ...).
MerchantID string
// TerminalID is the terminal/acceptor identifier issued by the PSP.
TerminalID string
// Username is the terminal user name (Mellat, IranKish acceptor id,
// AsanPardakht usr).
Username string
// Password is the terminal password (Mellat, IranKish, AsanPardakht pwd).
Password string
// MerchantKey is the API key, token or private key of the terminal
// (Zarinpal merchant_id, Zibal merchant, Pay.ir api, PayPing token,
// Pasargad RSA private key in PEM, ...).
MerchantKey string
// IBAN is the settlement account used by multiplexing capable gateways.
IBAN string
// Extra carries provider specific credentials that do not fit above.
Extra map[string]string
}
Config carries the merchant credentials of a single terminal. Not every gateway uses every field; the README documents the mapping per gateway and each implementation validates what it needs at construction time.
type Currency ¶
type Currency uint8
Currency identifies the unit an Money.Amount is expressed in.
Iranian gateways are inconsistent: most of them expect Rial (IRR) while a few (PayPing, some Zarinpal setups) expect Toman (IRT). Payvand always keeps the caller's unit explicit and lets every gateway convert to whatever its own API expects, so the caller never has to multiply or divide by ten.
type Doer ¶
type Doer interface {
// Do executes an HTTP request and returns its response.
Do(req *http.Request) (*http.Response, error)
}
Doer is the subset of http.Client Payvand needs. Supplying your own implementation is how you plug in tracing, mocking or a proxy.
type Error ¶
type Error struct {
// Gateway is the gateway name, e.g. "zarinpal".
Gateway Name
// Op is the operation that failed: "purchase", "verify", "refund",
// "inquiry" or "callback".
Op string
// Code is the gateway specific status/result code, if any.
Code string
// Message is the human readable message returned by the gateway.
Message string
// Err is the wrapped sentinel error.
Err error
}
Error is the rich error every gateway returns. It keeps the raw gateway code and message so the caller can log or map them, while still unwrapping to one of the sentinel errors above.
func (*Error) WithMessage ¶
WithMessage returns a copy of the error carrying the gateway message.
type Factory ¶
Factory builds a gateway from a terminal configuration and a set of options. Every gateway package registers one from its init function.
type Gateway ¶
type Gateway interface {
// Name returns the canonical gateway name.
Name() Name
// Capabilities reports which optional operations this gateway supports.
Capabilities() Capabilities
// Purchase creates a new payment on the provider side and returns the
// token plus everything needed to send the payer to the bank page.
Purchase(ctx context.Context, req PurchaseRequest) (PurchaseResponse, error)
// Verify settles a payment the payer has just completed. It must be
// called after the callback: most Iranian gateways roll a transaction
// back automatically when it is never verified.
Verify(ctx context.Context, req VerifyRequest) (VerifyResponse, error)
// Refund reverses an already verified payment, fully or partially
// depending on the provider.
Refund(ctx context.Context, req RefundRequest) (RefundResponse, error)
// Inquiry asks the provider for the current state of a transaction. It is
// the recovery path when a callback is lost.
Inquiry(ctx context.Context, req InquiryRequest) (InquiryResponse, error)
// ParseCallback extracts the provider's callback parameters out of the
// incoming HTTP request into a provider independent [Callback].
ParseCallback(r *http.Request) (Callback, error)
}
Gateway is the single abstraction every payment provider implements: the strategy interface of the package. Switching provider is switching the value behind this interface, never the call sites.
Operations a provider does not offer return an error wrapping ErrNotSupported; ask Gateway.Capabilities beforehand to avoid the round trip.
type InquiryRequest ¶
type InquiryRequest struct {
// Token is the payment token of the transaction to inspect.
Token string
// OrderID is the merchant side order identifier.
OrderID string
// ReferenceNumber is the bank reference number, when the provider keys on
// it instead of the token.
ReferenceNumber string
// TraceNumber is the system trace audit number, when required.
TraceNumber string
// Amount is the expected amount, required by the few providers that use
// it as part of the lookup key.
Amount Money
// Extra carries provider specific fields.
Extra map[string]string
}
InquiryRequest is the provider independent input of Gateway.Inquiry.
func (InquiryRequest) Get ¶
func (r InquiryRequest) Get(key string) string
Get returns the Extra entry for key, or "" when it is absent.
type InquiryResponse ¶
type InquiryResponse struct {
// Status is the normalised transaction status.
Status Status
// ReferenceNumber is the bank reference number, when the transaction got
// that far.
ReferenceNumber string
// CardNumber is the masked PAN, when known.
CardNumber string
// Amount is the transaction amount as reported by the provider.
Amount Money
// PaidAt is the payment time reported by the provider, when known.
PaidAt time.Time
// Message is the provider's own description of the status.
Message string
// Raw is the untouched provider response body.
Raw string
}
InquiryResponse is the provider independent output of Gateway.Inquiry.
type Logger ¶
type Logger interface {
// Debug reports a normal event, e.g. an outgoing gateway call.
Debug(ctx context.Context, msg string, fields map[string]string)
// Error reports a failed event together with the causing error.
Error(ctx context.Context, msg string, err error, fields map[string]string)
}
Logger is the hook Payvand uses to report outgoing calls. It is deliberately tiny so any logging library can satisfy it without an adapter package; the standard library is covered by SlogLogger.
type Money ¶
type Money struct {
// Amount is the numeric value expressed in Currency.
Amount int64
// Currency is the unit of Amount. The zero value is [IRR].
Currency Currency
}
Money is an amount bound to the unit it was expressed in.
Build it with Rial or Toman instead of using the struct literal, so the unit is always stated at the call site:
req.Amount = payvand.Toman(15_000)
func SettledAmount ¶ added in v1.2.0
SettledAmount reconciles the amount a provider reports for a payment with the amount the caller asked to verify, and is the amount every Gateway.Verify must report.
Verifying is the moment the merchant learns what it was actually paid, and the request amount comes from the merchant's own order while the reported one comes from the provider. When they disagree, the payment settled for something other than what was ordered — an error wrapping ErrAmountMismatch is the only safe answer, because the alternative is a caller shipping goods against a cheaper payment replayed onto the order.
A non-positive amount on either side means "not stated": providers that echo no amount leave nothing to compare, and callers that pass none are asking to be told rather than checked. Gateways whose provider always states an amount should reject a missing one themselves rather than rely on this.
func (Money) Equal ¶ added in v1.2.0
Equal reports whether two amounts are worth the same, whatever unit each of them is expressed in.
type Name ¶
type Name string
Name is the canonical identifier of a payment gateway, e.g. "zarinpal". It is what Register and New key on and what appears in Error.
func Registered ¶
func Registered() []Name
Registered returns the sorted names of all linked gateways.
type NopLogger ¶
type NopLogger struct{}
NopLogger is the default logger: it drops everything.
type Option ¶
type Option func(*Options)
Option mutates Options. Gateway packages expose their own typed options with this same signature, so transport and gateway settings compose in one variadic list.
func WithBaseURL ¶
WithBaseURL overrides the provider API host, mainly for tests and sandboxes.
func WithHTTPClient ¶
WithHTTPClient sets the HTTP client used for every call.
func WithHeader ¶
WithHeader adds a header sent with every request.
func WithSandbox ¶
WithSandbox switches gateways that have a test environment to it.
func WithSkipTLSVerify ¶
WithSkipTLSVerify disables TLS certificate verification.
func WithTimeout ¶
WithTimeout bounds a single gateway call.
func WithUserAgent ¶
WithUserAgent overrides the User-Agent header.
type Options ¶
type Options struct {
// HTTPClient performs the outgoing calls.
HTTPClient Doer
// Timeout bounds a single gateway call.
Timeout time.Duration
// Logger receives request/response events.
Logger Logger
// Sandbox switches gateways that offer a test environment to it.
Sandbox bool
// BaseURL overrides the provider API host. It exists for sandboxes,
// on-premise deployments and tests; leave it empty in production.
BaseURL string
// UserAgent is sent as the User-Agent header.
UserAgent string
// Headers are extra headers added to every request.
Headers map[string]string
// Retry is the transport retry policy.
Retry RetryPolicy
// SkipTLSVerify disables certificate verification. Some Shaparak hosts
// still serve incomplete chains; keep it false unless you must.
SkipTLSVerify bool
// contains filtered or unexported fields
}
Options holds every setting shared by all gateways. Build it with Option values; the zero value is completed by NewOptions.
func NewOptions ¶
NewOptions applies opts on top of the defaults and returns the result.
type PurchaseRequest ¶
type PurchaseRequest struct {
// Amount is the amount to charge, in the unit the caller chose. It is
// converted to whatever the provider expects.
Amount Money
// OrderID is the merchant side unique identifier of the order. Several
// gateways (Sadad, Parsian, Mellat, TOP, Sepehr) require it and some of
// them require it to be numeric; it is also the key used to reconcile a
// lost callback through [Gateway.Inquiry].
OrderID string
// CallbackURL is the absolute URL the bank returns the payer to.
CallbackURL string
// Description is a free text shown on the bank page or the receipt.
Description string
// Mobile is the payer's mobile number in "09xxxxxxxxx" form. Gateways use
// it to pre-fill the saved-card list.
Mobile string
// Email is the payer's email address, used by the few gateways that send
// receipts.
Email string
// NationalID is the payer's national code, used by gateways that match it
// against the card holder.
NationalID string
// PayerName is the payer's full name, when the provider accepts it.
PayerName string
// AllowedCards restricts the payment to these PANs, when supported.
AllowedCards []string
// Metadata carries additional provider specific key/value pairs.
Metadata map[string]string
}
PurchaseRequest is the provider independent input of Gateway.Purchase.
type PurchaseResponse ¶
type PurchaseResponse struct {
// Token is the payment token/authority/reference issued by the provider.
// It is the value to persist next to the order.
Token string
// OrderID echoes the order id the payment was created for.
OrderID string
// Redirect describes how to send the payer to the bank page.
Redirect Redirect
// Amount is the amount the payment was created for.
Amount Money
// Extra carries provider specific values that must be persisted next to
// the order because verification needs them again, such as the Pasargad
// invoice date.
Extra map[string]string
// Raw is the untouched provider response body, for logging and support.
Raw string
}
PurchaseResponse is the provider independent output of Gateway.Purchase.
func (PurchaseResponse) Get ¶
func (r PurchaseResponse) Get(key string) string
Get returns the Extra entry for key, or "" when it is absent.
type Redirect ¶
type Redirect struct {
// Method is http.MethodGet or http.MethodPost.
Method string
// URL is the bank page to open.
URL string
// Params are the form fields to post when Method is POST. They are
// already appended to URL when Method is GET.
Params map[string]string
}
Redirect describes how the payer's browser must reach the bank page.
Some providers hand out a plain URL (GET), others require an HTML form to be posted (Mellat, Sepehr, AsanPardakht). Both cases are expressed here so the caller writes the same three lines for every gateway:
res, _ := gw.Purchase(ctx, req) res.Redirect.Send(w, r)
func (Redirect) HTML ¶
HTML renders an auto-submitting HTML form for a POST redirect. For a GET redirect it renders a form as well, so the output is always usable, but callers normally use Redirect.String in that case.
func (Redirect) Send ¶
Send hands the payer over to the bank: an HTTP 303 for GET redirects, an auto-submitting form for POST redirects.
type RefundRequest ¶
type RefundRequest struct {
// Token is the payment token of the transaction to refund.
Token string
// OrderID is the merchant side order identifier.
OrderID string
// TransactionID is the provider transaction identifier returned by
// [Gateway.Verify].
TransactionID string
// ReferenceNumber is the bank reference number of the transaction.
ReferenceNumber string
// TraceNumber is the system trace audit number of the transaction.
TraceNumber string
// Amount is the amount to refund. Leave it zero for a full refund on
// gateways that support partial refunds.
Amount Money
// Reason is an optional free text kept by some providers.
Reason string
// Extra carries provider specific fields.
Extra map[string]string
}
RefundRequest is the provider independent input of Gateway.Refund.
func (RefundRequest) Get ¶
func (r RefundRequest) Get(key string) string
Get returns the Extra entry for key, or "" when it is absent.
type RefundResponse ¶
type RefundResponse struct {
// RefundID is the provider identifier of the refund, when one is issued.
RefundID string
// Amount is the refunded amount as reported by the provider.
Amount Money
// Raw is the untouched provider response body.
Raw string
}
RefundResponse is the provider independent output of Gateway.Refund.
type RetryPolicy ¶
type RetryPolicy struct {
// MaxAttempts is the total number of attempts, including the first one.
// Values below one disable retrying.
MaxAttempts int
// Backoff is the pause between attempts. It is doubled after every retry.
Backoff time.Duration
}
RetryPolicy controls how transport level failures are retried, on network errors and 5xx responses.
It applies to the calls where the worst case is asking the same question twice: creating a payment token, verifying one, reading a status. Refunds and reversals are excluded, since a retry there could send the money back a second time, and the providers do not all deduplicate that.
type SlogLogger ¶
type SlogLogger struct {
// Logger is the destination logger. A nil value disables logging.
Logger *slog.Logger
}
SlogLogger adapts a standard library slog.Logger to Logger.
type Status ¶
type Status uint8
Status is the normalised life cycle state of a transaction.
const ( // StatusUnknown means the provider did not report a state Payvand can map. StatusUnknown Status = iota // StatusPending means the payer has not finished the payment yet. StatusPending // StatusPaid means the money was taken from the payer but the transaction // is not verified/settled yet. StatusPaid // StatusVerified means the transaction is verified and settled. StatusVerified // StatusFailed means the transaction failed or was rejected. StatusFailed // StatusCanceled means the payer aborted the payment. StatusCanceled // StatusRefunded means the transaction was refunded or reversed. StatusRefunded )
type Unsupported ¶
type Unsupported struct {
// GatewayName is reported in the returned errors.
GatewayName Name
}
Unsupported implements the optional half of Gateway with "not supported" answers. A gateway embeds it and overrides only the operations its provider actually offers, which keeps every implementation free of boilerplate while still satisfying the single interface callers program against.
func (Unsupported) Inquiry ¶
func (u Unsupported) Inquiry(context.Context, InquiryRequest) (InquiryResponse, error)
Inquiry reports that the provider has no transaction status API.
func (Unsupported) ParseCallback ¶
func (u Unsupported) ParseCallback(*http.Request) (Callback, error)
ParseCallback reports that the provider does not call back into the merchant server, e.g. in-app gateways that report the result to the mobile client.
func (Unsupported) Refund ¶
func (u Unsupported) Refund(context.Context, RefundRequest) (RefundResponse, error)
Refund reports that the provider has no refund API.
type VerifyRequest ¶
type VerifyRequest struct {
// Token is the payment token returned by [Gateway.Purchase].
Token string
// OrderID is the merchant side order identifier.
OrderID string
// Amount is the amount that was requested. Gateways that do not echo the
// amount reuse this value, and those that do compare against it.
Amount Money
// ReferenceNumber is the bank reference (RRN) coming from the callback,
// required by IranKish, Mellat and Saman.
ReferenceNumber string
// TraceNumber is the system trace audit number coming from the callback,
// required by IranKish and Mellat.
TraceNumber string
// CardNumber is the masked PAN coming from the callback, echoed back by
// gateways that do not return it on verification.
CardNumber string
// Extra carries provider specific callback fields (Sepehr digital
// receipt, AsanPardakht payGateTranId, ...).
Extra map[string]string
}
VerifyRequest is the provider independent input of Gateway.Verify. Fill it from the persisted payment plus the parsed Callback.
func (VerifyRequest) Get ¶
func (r VerifyRequest) Get(key string) string
Get returns the Extra entry for key, or "" when it is absent.
type VerifyResponse ¶
type VerifyResponse struct {
// ReferenceNumber is the final bank reference number (RRN) to show to the
// payer and to keep for reconciliation.
ReferenceNumber string
// TransactionID is the provider side transaction identifier, when it
// differs from the reference number (used by refunds).
TransactionID string
// OrderID echoes the merchant side order identifier.
OrderID string
// CardNumber is the masked PAN of the card that paid.
CardNumber string
// CardHash is the hashed PAN, when the provider returns one.
CardHash string
// Amount is the settled amount as reported by the provider.
Amount Money
// Fee is the provider fee deducted from the amount, in Rial, when known.
Fee int64
// PaidAt is the settlement time reported by the provider, when known.
PaidAt time.Time
// Raw is the untouched provider response body.
Raw string
}
VerifyResponse is the provider independent output of Gateway.Verify. A nil error means the money is settled to the merchant.