Building Seamless Payment Integrations with Frappe Payments
In today's digital economy, offering smooth payment processing is crucial for any business application. Frappe's Payments app provides an elegant framework for integrating various payment gateways.

Payments app is a best example to maintaining clean code architecture. Let's explore how this system works and how you can extend it for custom payment gateways.
The Power of Frappe's Payment Integration Structure
Frappe's payment integration follows a well-designed pattern that makes adding new gateways straightforward while keeping the implementation clean and maintainable. The key components of this architecture are:
- Standardized Settings Documents: Each payment gateway has its own Settings document
- Gateway Controller Classes: Python classes that handle gateway-specific logic
- Payment URL Generation: A consistent interface for initiating payments
The Settings Document Pattern
Every payment gateway integration follows a naming convention where the settings document is named [PaymentGatewayName] Settings. For example:
- Razorpay Settings
- PayPal Settings
This pattern makes it easy to locate and manage gateway configurations.
How It Works
1.Each gateway has a Settings DocType
- Example: CCAvenue Settings, Razorpay Settings
- Stores API keys and configurations.
2. A matching Python class
- Example: CCAvenueSettings, RazorpaySettings
- Contains gateway-specific logic.
3. Standard methods
get_payment_url()
– Generates the payment link.validate_transaction_currency()
– Checks if the currency is supported.
The Gateway Controller Class
Each payment gateway has a corresponding Python class that handles its specific logic. The class name matches the settings document name (without spaces). Here's the basic structure:
class PaymentGatewaySettings(Document):
def validate_transaction_currency(self, currency):
# Validate if currency is supported
pass
def get_payment_url(self, **kwargs):
# Generate payment URL for this gateway
pass
The magic happens in the get_payment_gateway_controller
function from payments/utils/utils.py
:
# payment_gatway parameters having a same value which you have selected in Payment Getway field
def get_payment_gateway_controller(payment_gateway):
"""Return payment gateway controller"""
gateway = frappe.get_doc("Payment Gateway", payment_gateway)
if gateway.gateway_controller is None:
try:
return frappe.get_doc(f"{payment_gateway} Settings")
except Exception:
frappe.throw(_("{0} Settings not found").format(payment_gateway))
else:
try:
return frappe.get_doc(gateway.gateway_settings, gateway.gateway_controller)
except Exception:
frappe.throw(_("{0} Settings not found").format(payment_gateway))
- Takes a payment gateway name as input
- Looks up the Payment Gateway record
- Returns the appropriate settings document/controller class
- Provides clear error handling if the gateway isn't properly configured
Implementing a Custom Payment Gateway
Let's walk through adding a new payment gateway called "CCAvenue" to demonstrate how cleanly this architecture extends.
1. create a settings doctype
Naming of the Setting doctype: CCAvenue Settings
Add a necessary fields
2. Implement the Controller Class
This class should have a method get_payment_url
import frappe
from frappe.model.document import Document
class CCAvenueSettings(Document):
def validate(self):
self.validate_credentials()
def validate_credentials(self):
# Test the API keys are valid
# Write a code to validate credential with payment getway
pass
def get_payment_url(self, **kwargs):
# Implement the logic to call payment getway api
pass
Key Benefits of This Architecture
- Consistency: All payment gateways follow the same pattern
- Extensibility: Easy to add new gateways without modifying core code
- Maintainability: Gateway-specific code is isolated in its own class
- Reusability: Common utilities are shared across all integrations
No comments yet. Login to start a new discussion Start a new discussion