Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Magento 2 Custom Payment Gateway Integration

By Kokil Thapa | Last reviewed: August 2026

Integrating a local or niche payment provider often requires building a bespoke solution because standard extensions rarely support regional APIs or specific compliance needs. A successful Magento 2 custom payment gateway integration demands strict adherence to the platform's modular architecture, secure credential storage, and robust transaction state management. This guide covers the exact implementation patterns I use when connecting Magento 2.4.7+ to providers like eSewa, Khalti, or international gateways lacking official plugins.

How do you structure a Magento 2 custom payment gateway integration module?

Before writing any PHP logic, you must establish a clean module structure that follows Magento 2 conventions. On real client projects, I have seen too many integrations fail during upgrades because developers placed files in the wrong directories or skipped dependency injection configuration. For a professional eCommerce website developer in Nepal, getting this foundation right prevents months of technical debt.

Vendor/PaymentModuleetc/config.xmletc/system.xmlModel/Payment.phpController/Webhook.phpview/frontend/layoutregistration.phpDefines default config values& payment method codesAdmin panel configuration fields(API keys, test mode toggle)Core payment logic: authorize,capture, refund, voidHandles async payment callbacks& updates order status securelyCheckout page UI components& JS validation logic
Standard directory layout for a Magento 2 custom payment gateway integration module

Your module registration file registration.php must declare the component correctly. In etc/module.xml, set up sequence dependencies to ensure Magento_Payment and Magento_Sales load first. The most critical files are etc/config.xml and etc/system.xml. Config.xml defines your payment method code and default values, while system.xml renders the admin configuration interface. Never hardcode API endpoints or secrets in PHP classes; always read them through the scope config interface so merchants can switch between test and live modes per store view.

Registering the payment method safely

<!-- app/code/Vendor/PaymentModule/etc/config.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <default>
        <payment>
            <vendor_custom_gateway>
                <active>0</active>
                <model>Vendor\PaymentModule\Model\Payment</model>
                <title>Custom Local Gateway</title>
                <allowspecific>1</allowspecific>
                <specificcountry>NP</specificcountry>
                <sort_order>100</sort_order>
            </vendor_custom_gateway>
        </payment>
    </default>
</config>

This XML registers the method code vendor_custom_gateway and points to your model class. Setting allowspecific to 1 restricts availability to Nepal by default, which is useful for local gateways like eSewa or ConnectIPS that only work with NPR transactions.

What are the essential steps to implement secure transaction processing?

Secure transaction processing separates hobbyist code from production-ready integrations. When I build payment modules for legal-tech portals or eCommerce stores, security is non-negotiable because financial data breaches destroy trust instantly. Your payment model must extend \Magento\Payment\Model\Method\AbstractMethod and implement specific interfaces depending on whether you support authorization-only flows, direct capture, or refunds.

CustomerCheckout PageMagento 2Payment ModelGateway APIAuthorize/CaptureWebhookAsync NotificationSignatureVerificationOrder StatusUpdated Safely
Secure transaction lifecycle for Magento 2 custom payment gateway integration with webhook verification

The key methods you will override are authorize(), capture(), refund(), and void(). Each method receives a payment info object and an amount. You must validate the amount against the order total before making any external API call. Use Magento’s built-in HTTP client factory rather than raw cURL to respect proxy settings and timeout configurations defined in the admin panel.

Implementing the capture method with error handling

public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount)
{
    if (!$this->canCapture()) {
        throw new \Magento\Framework\Exception\LocalizedException(
            __('The capture action is not available.')
        );
    }

    $order = $payment->getOrder();
    $gatewayResponse = $this->gatewayClient->charge([
        'order_id'   => $order->getIncrementId(),
        'amount'     => round($amount * 100), // Convert to cents/paisa
        'currency'   => $order->getBaseCurrencyCode(),
        'return_url' => $this->getReturnUrl($order),
    ]);

    if ($gatewayResponse['status'] !== 'success') {
        throw new \Magento\Framework\Exception\LocalizedException(
            __($gatewayResponse['message'])
        );
    }

    $payment->setTransactionId($gatewayResponse['transaction_id'])
            ->setIsTransactionClosed(false);

    return $this;
}

Notice the explicit rounding and currency conversion. Many Nepalese gateways expect amounts in paisa (integer), while Magento stores base currency as decimal floats. Getting this wrong causes silent failures where Rs 1,500.00 becomes Rs 15.00 at the gateway. Always log the raw request and response using Magento’s logger interface for debugging, but never log sensitive card data or full authentication tokens.

How should you handle asynchronous webhooks and IPN callbacks?

Most modern payment providers use asynchronous webhooks to confirm transactions because browser redirects are unreliable. Customers close tabs, lose internet connectivity, or get redirected incorrectly on mobile devices. Your Magento 2 custom payment gateway integration must treat the webhook as the single source of truth for payment status, not the frontend redirect.

Create a dedicated controller action that accepts POST requests without CSRF tokens since external servers cannot obtain Magento form keys. However, you must verify every incoming request using HMAC signatures or shared secret hashing provided by the gateway. Skipping this verification allows attackers to forge successful payment notifications and receive goods without paying.

  • Validate the signature before parsing any payload data
  • Load the order by increment ID stored in the gateway metadata
  • Check current order state to prevent duplicate processing
  • Create invoice programmatically only if payment is confirmed
  • Add detailed comments to order history for audit trails
  • Return HTTP 200 quickly to prevent gateway retries

Secure webhook controller implementation

public function execute()
{
    $rawBody = $this->getRequest()->getContent();
    $signature = $this->getRequest()->getHeader('X-Gateway-Signature');

    if (!$this->signatureValidator->isValid($rawBody, $signature)) {
        $this->logger->critical('Invalid webhook signature received');
        return $this->resultFactory->create(ResultFactory::TYPE_RAW)
            ->setHttpResponseCode(403);
    }

    $data = json_decode($rawBody, true);
    $order = $this->orderRepository->getByIncrementId($data['merchant_order_id']);

    if ($order->getState() === Order::STATE_PROCESSING) {
        return $this->jsonResponse(['status' => 'already_processed']);
    }

    if ($data['payment_status'] === 'completed') {
        $this->invoiceService->createInvoiceAndNotify($order, $data['txn_id']);
    }

    return $this->jsonResponse(['status' => 'success']);
}

This pattern ensures idempotency. If the gateway sends the same notification three times due to network timeouts, only the first one creates an invoice. Subsequent calls return success without modifying order state. For more complex scenarios involving multiple payment attempts, consider reading about Laravel payment integrations which share similar webhook security principles applicable across PHP frameworks.

Which configuration options matter most for production deployments?

Configuration management determines whether your integration survives environment changes, team handoffs, and PCI audits. In practice, I have debugged countless production issues caused by hardcoded URLs, missing test mode flags, or credentials stored in plain text within version control. Your system.xml should expose granular controls that operations teams can adjust without code deployments.

Configuration FieldPurposeSecurity Note
Test Mode ToggleSwitches API endpoint between sandbox and productionPrevents accidental live charges during development
API Secret KeyAuthenticates outbound requests to gatewayUse backend model to encrypt value in database
Webhook SecretValidates inbound notification signaturesNever expose in frontend JavaScript or logs
Allowed CurrenciesRestricts method to specific currency codesBlocks invalid cross-currency transaction attempts
Debug LoggingEnables verbose request/response loggingAuto-disable in production or mask sensitive fields
Order Status MappingMaps gateway states to Magento order statusesEnsures consistent fulfillment workflow triggers

Always implement a backend model for sensitive fields. Magento provides \Magento\Config\Model\Config\Backend\Encrypted which automatically encrypts values using the deployment-specific encryption key. When migrating between environments, export these encrypted values separately or re-enter them manually. Never commit unencrypted secrets to Git repositories even if they appear masked in the admin panel.

How do you test Magento 2 custom payment gateway integration thoroughly?

Testing payment integrations requires layered verification beyond unit tests. While PHPUnit validates individual method logic, integration tests must simulate actual gateway behavior including timeouts, partial failures, and malformed responses. On client projects targeting Nepal markets, I maintain separate test suites for eSewa, Khalti, and IME Pay because each has unique edge cases around NPR formatting and callback timing.

Manual QAReal gateway sandboxIntegration TestsMocked HTTP + DB assertionsUnit TestsSignature validation, amount math, config parsingSlow, expensive,catches real bugsValidates full flowwithout live chargesFast, isolated,runs on every commit
Layered testing approach ensuring reliability for Magento 2 custom payment gateway integration

Start with unit tests for pure functions: signature verification, amount conversion, and config retrieval. These run instantly and catch regressions when upgrading Magento versions. Next, write integration tests using Magento’s Test Framework that mock the HTTP client but exercise real database interactions. Verify that invoices are created correctly, order comments are added, and email notifications trigger appropriately. Finally, perform manual QA against the gateway’s sandbox environment weekly to detect undocumented API changes that automated mocks would miss.

For teams managing multiple eCommerce platforms, understanding these testing patterns transfers well. The discipline required for reliable Shopify vs WooCommerce comparisons applies equally here: assume nothing works until proven through executable specifications rather than hopeful assumptions.

Final recommendations for production readiness

Building a robust Magento 2 custom payment gateway integration requires attention to architectural details that documentation often glosses over. Encrypt all credentials, verify every webhook signature, handle currency conversions explicitly, and maintain comprehensive test coverage across all three testing layers. Document your module’s configuration requirements clearly so future maintainers understand why certain settings exist and what breaks if they change.

If you are planning a payment integration for a Nepal-based store or need help debugging an existing implementation that fails intermittently in production, reach out through my contact page. I regularly help businesses integrate local payment providers securely and can review your module architecture before you ship to customers.

Frequently Asked Questions

Magento Open Source 2.4.7 or Adobe Commerce 2.4.7+ is required, running on PHP 8.2 or higher with MySQL 8.0/8.4 LTS.

Typically NPR 150,000–300,000 (USD 1,100–2,200) depending on API complexity, webhook handling, and testing requirements.

Only when no maintained extension supports your provider, or specific business logic like split payments requires deep customization.

Create app/code/Vendor/Gateway with registration.php, module.xml, etc/config.xml for defaults, etc/payment.xml for method definition, Model/Gateway.php for API logic, Controller/Payment/Callback.php for webhooks, and view/frontend/layout files for checkout UI. Follow PSR-4 autoloading strictly and declare dependencies on Magento_Payment and Magento_Checkout modules to ensure proper load order during deployment.

Never store raw card data; use tokenization or redirect flows. Implement HMAC signature verification on all callbacks, enforce HTTPS-only endpoints, validate transaction amounts server-side against order totals, log audit trails without sensitive data, and comply with PCI-DSS SAQ-A if using hosted fields. In my experience building legal-tech portals handling sensitive transactions, treating every callback as potentially malicious prevents most fraud vectors.

Create a dedicated controller implementing CsrfAwareActionInterface to accept POST callbacks, verify signatures before processing, use database transactions when updating order status, return HTTP 200 only after successful persistence, and implement idempotency keys to prevent duplicate processing. Add retry logic with exponential backoff for failed updates, and monitor dead-letter queues for unrecoverable failures that require manual intervention.

Verify etc/payment.xml uses correct group and sort_order attributes, check system.xml defines config paths matching payment.xml codes, clear generated/metadata and var/cache directories, run bin/magento setup:upgrade, and confirm module appears enabled in app/etc/config.php. Missing ACL resources in etc/acl.xml also hide configuration sections from non-admin users, which I have encountered repeatedly during client handovers.

Use sandbox credentials provided by your gateway, mock HTTP responses with Guzzle handlers during unit tests, create test orders via CLI using bin/magento dev:tests:run, and configure separate sandbox/live API keys in env.php rather than hardcoding. For Nepal gateways like eSewa or Khalti, request dedicated test merchant accounts since shared sandboxes often have rate limits that block automated testing workflows.

Magento CSRF protection rejects external POST requests lacking valid form keys. Implement CsrfAwareActionInterface in your callback controller, override validateForCsrf to return true for verified webhook signatures, and never disable CSRF globally. This error frequently occurs when developers copy frontend form patterns to backend API controllers without understanding that payment gateways cannot provide Magento session tokens.

Store base currency amounts in sales_order_payment, convert display amounts using Magento\Framework\Pricing\PriceCurrencyInterface, pass converted values to gateway APIs respecting their decimal precision rules, and handle currency mismatch validation in callbacks. For multi-currency stores like Petals Nepal serving international customers, always reconcile gateway-settled amounts against expected conversions to detect exchange rate discrepancies before marking orders complete.

Yes, but expect incomplete documentation and REST APIs requiring custom HMAC implementations rather than standard OAuth. Build adapter classes abstracting each gateway's quirks behind a common interface, handle NPR-only constraints where applicable, and account for bank-maintenance windows affecting callback delivery. On projects integrating these providers, I allocate extra time for reverse-engineering request formats since official SDKs rarely exist for Magento 2.

Enable monolog logging to var/log/payment_gateway.log with context arrays containing masked request/response data, add X-Request-ID headers for tracing across systems, instrument New Relic or Sentry for exception tracking, and create admin grid showing recent callback statuses with raw payloads. Silent failures usually stem from uncaught exceptions in callback controllers returning HTTP 500, causing gateways to stop retrying without visible errors in Magento admin.

Avoid synchronous API calls during checkout submit; defer non-critical operations to async queues using Magento\Framework\MessageQueue. Cache gateway configuration in Redis with appropriate TTLs, index payment-related EAV attributes if querying frequently, and profile callback controllers separately since they bypass normal page caching. Slow payment processing directly impacts conversion rates, so I measure gateway response times independently from Magento render performance.

Implement \Magento\Payment\Model\Method\Adapter refund and void methods calling gateway APIs with original transaction references, update creditmemo entities atomically within database transactions, and emit sales_order_creditmemo_save_after events for downstream observers. Partial refund support requires storing line-item-level transaction mappings since many Nepal gateways lack granular refund APIs, necessitating manual reconciliation workflows for complex returns.

Custom modules require manual updates for every Magento security patch, PHP version upgrade, and gateway API change without vendor support. Budget 20–30 hours annually for compatibility testing, dependency updates, and regression fixes. Extensions receive upstream maintenance but may lag on new features. In practice, I recommend custom builds only when long-term ownership capacity exists; otherwise, sponsor feature development in open-source extensions to share maintenance costs across the community.

Share this article

Quick Contact Options
Choose how you want to connect me: