For globally distributed SaaS and e-commerce platforms, depending on a single payment processor is a risk. Different regions require different gateways—for instance, CyberSource is popular in the US, HyperPay dominates the Middle East, and various e-wallets are required in Southeast Asia. Building a separate code path for each gateway quickly turns your codebase into spaghetti.
The solution is to design a Polymorphic Payments Interface that abstract gateways into standard interfaces, hiding provider-specific payload formatting.
1. Defining the Payment Gateway Interface
The payment gateway interface should be designed around business operations rather than provider terminology:
type PaymentGateway interface {
Authorize(ctx context.Context, req *AuthorizationRequest) (*AuthorizationResponse, error)
Capture(ctx context.Context, req *CaptureRequest) (*CaptureResponse, error)
Refund(ctx context.Context, req *RefundRequest) (*RefundResponse, error)
}
2. Writing Concrete Adapters
Each gateway (e.g., CyberSource, HyperPay) implements this interface. A factory pattern resolves the correct adapter based on the user's region or payment method:
type CyberSourceAdapter struct {
merchantID string
apiKey string
}
func (a *CyberSourceAdapter) Authorize(ctx context.Context, req *AuthorizationRequest) (*AuthorizationResponse, error) {
// Translate standard AuthorizationRequest to CyberSource XML/JSON payload
// Execute HTTP POST and parse response
return &AuthorizationResponse{
TransactionID: "cs_12345",
Approved: true,
}, nil
}
Using this pattern, adding a new payment gateway is as simple as writing a new adapter and registering it with the factory, leaving the core checkout logic untouched.