2023-07-19 10:00:00+00:00

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.