# Custom payment gateway

> Connect a payment provider Merx does not ship with.

Use the [`gateways`](https://merx2.wagnerwagner.de/reference/options/config-options/gateways.md) option to add your own payment gateway.

A gateway is a pair of functions. `initializePayment` starts the payment when the checkout is submitted, and `completePayment` finishes it once the provider sends the customer back. Both receive the [virtual order page](https://merx2.wagnerwagner.de/guide/glossary.md) and must return an [`OrderPage`](https://merx2.wagnerwagner.de/reference/classes/order-page.md) object.

In `initializePayment` you can set a `redirect` URL. The customer will be sent there. If you don’t set `redirect`, they go to [`/api/shop/success`](https://merx2.wagnerwagner.de/reference/api/routes/shop-success-get.md) directly, which is what an invoice order does.

Whatever return address you hand your provider has to be [`Merx::returnUrl()`](https://merx2.wagnerwagner.de/reference/classes/merx/return-url.md). It carries a one-time nonce that ties the return to the checkout that started it — `/api/shop/success` completes a payment on a `GET`, and without that check a link on any page could complete a pending payment of somebody who approved it at the provider but never came back. A return that arrives without the nonce is refused with [`error.merx.invalidReturn`](https://merx2.wagnerwagner.de/reference/options/translations/error-merx-invalidreturn.md).

```php title="/site/config/config.php"
<?php

use Kirby\Data\Yaml;
use Wagnerwagner\Merx\OrderPage;
use Wagnerwagner\Merx\PaymentDetails;

return [
  'wagnerwagner.merx.gateways' => [
    'my-payment-provider' => [
      'initializePayment' => function(OrderPage $virtualOrderPage): OrderPage
      {
        // Ask your provider for a payment URL for the amount of
        // $virtualOrderPage->cart()->total()
        $redirectUrl = 'https://mypaymentprovider.com/payment/mlDquvqMQK85M1Pw';

        $virtualOrderPage->version()->update([
          'redirect' => $redirectUrl,
        ]);
        return $virtualOrderPage;
      },
      'completePayment' => function(OrderPage $virtualOrderPage, array $data): OrderPage
      {
        $virtualOrderPage->version()->update([
          'datePaid' => date('c'),
          'paymentDetails' => Yaml::encode(PaymentDetails::create([
            'provider' => 'my-payment-provider',
            'id' => $data['transaction_id'] ?? null,
          ])),
        ]);
        return $virtualOrderPage;
      }
    ],
  ],
];
```

Be careful. `$virtualOrderPage` is just virtual and not yet written to the content folder, so you can’t use [`$page->update()`](https://getkirby.com/docs/reference/objects/page/update) (which would write a content file). Instead you can use [`$page->version()->update()`](https://getkirby.com/docs/reference/objects/content/version/update) to update the virtual page’s content.

> [!CAUTION]
> Build the payment record with [`PaymentDetails::create()`](https://merx2.wagnerwagner.de/reference/classes/payment-details/create.md) instead of storing your provider’s response as it is. A raw response carries the customer’s name, email and address, and order pages are plain content files.
