---
title: "Building Custom Gateway"
space: "Integrations"
url: "https://integrations.frappe.cloud/integrations/payment-integration/payment-core/building-custom-gateway"
updated: "2026-07-30"
---

## 1. Gateway Settings Doctype

Create gateway settings doctype extending standard controller pattern:

```python
class CustomGatewaySettings(Document):
    supported_currencies = ("USD", "EUR", "GBP")

    def on_update(self):
        create_payment_gateway(
            "Custom Gateway",
            settings="Custom Gateway Settings",
            controller=self.name
        )
        call_hook_method("payment_gateway_enabled", gateway="Custom Gateway")

    def validate_transaction_currency(self, currency):
        if currency not in self.supported_currencies:
            frappe.throw(_("Currency {0} not supported").format(currency))

    def get_payment_url(self, **kwargs):
        return f"https://checkout.customgateway.com/pay?{urlencode(kwargs)}"
```

## 2. Doctype JSON Configuration

```json
{
  "doctype": "DocType",
  "name": "Custom Gateway Settings",
  "fields": [
    {"fieldname": "api_key", "fieldtype": "Data", "label": "API Key"},
    {"fieldname": "api_secret", "fieldtype": "Password", "label": "API Secret"},
    {"fieldname": "use_sandbox", "fieldtype": "Check", "label": "Use Sandbox"}
  ]
}
```

## 3. Checkout Template

Create checkout page template:

```html
{% extends "templates/web.html" %}
{% block page_content %}
<form id="payment-form" action="{{ callback_url }}" method="POST">
    <input type="hidden" name="token" value="{{ token }}">
    <button type="submit">Pay {{ amount }} {{ currency }}</button>
</form>
{% endblock %}
```

## 4. Callback Handler

```python
@frappe.whitelist(allow_guest=True)
def handle_callback():
    token = frappe.form_dict.token
    integration_request = frappe.get_doc("Integration Request", token)

    # Verify payment with gateway
    payment_status = verify_payment_with_gateway(token)

    integration_request.update_status(
        data={"transaction_id": payment_status.txn_id},
        status="Completed" if payment_status.success else "Failed"
    )

    # Trigger callback on reference document
    data = json.loads(integration_request.data)
    if data.get("reference_doctype"):
        doc = frappe.get_doc(data["reference_doctype"], data["reference_docname"])
        doc.run_method("on_payment_authorized", "Completed")
```

## 5. App Hooks Configuration

```python
# custom_gateway/hooks.py
doctype_js = {
    "Custom Gateway Settings": "custom_gateway/js/settings.js"
}

payment_gateway_controller = {
    "Custom Gateway": "custom_gateway.controllers.get_controller"
}
```