Add a custom field for an offline payment method

Customize Checkout

This topic describes how to add a custom field to an offline payment method in the payment step of the checkout. The custom field allows the buyer to enter a comment about a purchase order.

Prerequisites

The Purchase Order payment method must be enabled in the storefront for this task. Ensure this payment method is enabled by navigating to Stores > Settings > Configuration > Sales > Payment Methods > Other Payment Methods > Purchase Order in the Admin.

You must perform following steps to add a custom field to an offline payment method:

  1. Create a new module.
  2. Add a db_schema.xml file.
  3. Add a requirejs file to the module.
  4. Override the vendor files.
  5. Add an Observer.
  6. Compile and deploy the module.
  7. Verify that the module works.

Let’s go through each step.

Step 1: Create a new module

Create a new module named Learning/CustomField and register it.

Step 2 Add a db_schema.xml file

Add the paymentpocomment column in the quote_payment and sales_order_payment tables using the declarative schema method.

Create the app/code/Learning/CustomField/etc/db_schema.xml and define the declarative schema as follows:

1
2
3
4
5
6
7
8
9
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
    <table name="quote_payment" resource="checkout" engine="innodb" comment="Sales Flat Quote Payment">
        <column xsi:type="text" name="paymentpocomment" nullable="true" comment="PO Comment"/>
    </table>
    <table name="sales_order_payment" resource="sales" engine="innodb" comment="Sales Flat Order Payment">
        <column xsi:type="text" name="paymentpocomment" nullable="true" comment="PO Comment"/>
    </table>
</schema>

Step 3: Add a requirejs file to the module

Create the app/code/Learning/CustomField/view/frontend/requirejs-config.js file and add the following code:

1
2
3
4
5
6
7
 var config = {
    map: {
        '*': {
          'Magento_OfflinePayments/js/view/payment/offline-payments':'Learning_CustomField/js/view/payment/offline-payments',
        }
    }
}

Step 4: Override the vendor files

We must override the behavior of the following files to display the custom field:

Override the offline-payments.js

Override the Magento_OfflinePayments/view/frontend/web/js/view/payment/offline-payments.js file to change the renderer of the Purchase Order payment method.

Create the app/code/Learning/CustomField/view/frontend/web/js/view/payment/offline-payments.js file and add the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
define(
    [
        'uiComponent',
        'Magento_Checkout/js/model/payment/renderer-list'
    ],
    function (
        Component,
        rendererList
    ) {
        'use strict';
        rendererList.push(
            {
                type: 'checkmo',
                component: 'Magento_OfflinePayments/js/view/payment/method-renderer/checkmo-method'
            },
            {
                type: 'banktransfer',
                component: 'Magento_OfflinePayments/js/view/payment/method-renderer/banktransfer-method'
            },
            {
                type: 'cashondelivery',
                component: 'Magento_OfflinePayments/js/view/payment/method-renderer/cashondelivery-method'
            },
            {
                type: 'purchaseorder',
                component: 'Learning_CustomField/js/view/payment/method-renderer/purchaseorder-method'
            }
        );
        /** Add view logic here if needed */
        return Component.extend({});
    }
);

Override the purchaseorder-method.js

It is also necessary to override the Magento_OfflinePayments/view/frontend/web/js/view/payment/method-renderer/purchaseorder-method.js file.

The template path value used in this file must be altered to use the custom template. Also, the logic to get the additional_data is implemented in this file.

Create the app/code/Learning/CustomField/view/frontend/web/js/view/payment/method-renderer/purchaseorder-method.js file and add the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
define([
    'Magento_Checkout/js/view/payment/default',
    'jquery',
    'mage/validation'
], function (Component, $) {
    'use strict';

    return Component.extend({
        defaults: {
            template: 'Learning_CustomField/payment/purchaseorder-form',
            purchaseOrderNumber: ''
        },

        /** @inheritdoc */
        initObservable: function () {
            this._super()
                .observe('purchaseOrderNumber');

            return this;
        },

        /**
         * @return {Object}
         */
        getData: function () {
            return {
                method: this.item.method,
                'po_number': this.purchaseOrderNumber(),
                'additional_data': {
                    'po_number': $('#po_number').val(),
                    'paymentpocomment': $('#purchaseorder_paymentpocomment').val(),
                }
            };
        },

        /**
         * @return {jQuery}
         */
        validate: function () {
            var form = 'form[data-role=purchaseorder-form]';

            return $(form).validation() && $(form).validation('isValid');
        }
    });
});

Override the purchaseorder-form.html

We must override the Magento_OfflinePayments/view/frontend/web/template/payment/purchaseorder-form.html template file to add the custom input field (Purchase Order Comment).

Create the app/code/Learning/CustomField/view/frontend/web/template/payment/purchaseorder-form.html file and add the following code:

Show code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}">
    <div class="payment-method-title field choice">
        <input type="radio"
               name="payment[method]"
               class="radio"
               data-bind="attr: {'id': getCode()}, value: getCode(), checked: isChecked, click: selectPaymentMethod, visible: isRadioButtonVisible()"/>
        <label data-bind="attr: {'for': getCode()}" class="label">
            <span data-bind="text: getTitle()"></span>
        </label>
    </div>

    <div class="payment-method-content">
        <!-- ko foreach: getRegion('messages') -->
        <!-- ko template: getTemplate() --><!-- /ko -->
        <!--/ko-->
        <div class="payment-method-billing-address">
            <!-- ko foreach: $parent.getRegion(getBillingAddressFormName()) -->
            <!-- ko template: getTemplate() --><!-- /ko -->
            <!--/ko-->
        </div>
        <form id="purchaseorder-form" class="form form-purchase-order" data-role="purchaseorder-form">
            <fieldset class="fieldset payment method" data-bind='attr: {id: "payment_form_" + getCode()}'>
                <div class="field field-number required">
                    <label for="po_number" class="label">
                    <span><!-- ko i18n: 'Purchase Order Number'--><!-- /ko --></span>
                    </label>
                    <div class="control">
                    <div class="name-info">
                    <input type="text" id="po_number" name="payment[po_number]" placeholder="Purchase Order Number" data-validate="{required:true}"        data-bind='attr: {title: $t("Purchase Order Number")},value: purchaseOrderNumber' class="input-text"/>
                    <input type="text" id="purchaseorder_paymentpocomment" name="payment[paymentpocomment]" class="input-text" placeholder="Purchase Order Comment" value="" data-bind="attr: {title: $t('Purchase Order Comment'),'data-container': getCode() + '-paymentpocomment',valueUpdate: 'keyup' "/>
                        </div>
                    </div>
                </div>
            </fieldset>
        </form>
        <div class="checkout-agreements-block">
            <!-- ko foreach: $parent.getRegion('before-place-order') -->
                <!-- ko template: getTemplate() --><!-- /ko -->
            <!--/ko-->
        </div>
        <div class="actions-toolbar" id="review-buttons-container">
            <div class="primary">
                <button class="action primary checkout"
                        type="submit"
                        data-bind="
                        click: placeOrder,
                        attr: {title: $t('Place Order')},
                        enable: (getCode() == isChecked()),
                        css: {disabled: !isPlaceOrderActionAllowed()}
                        "
                        data-role="review-save">
                    <span data-bind="i18n: 'Place Order'"></span>
                </button>
            </div>
        </div>
    </div>
</div>

Step 5: Add an Observer

Create an Observer file to save the custom field data to the order. For the Observer file an events.xml file is required to call the observer for a particular event. For this example, the checkout_onepage_controller_success_action event is used.

Create the app/code/Learning/CustomField/etc/frontend/events.xml file and add the following code:

1
2
3
4
5
6
7
<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="checkout_onepage_controller_success_action">
        <observer name="paymentfields_paymentfields_observer_frontend_sales_orderpaymentsavebefore" instance="Learning\CustomField\Observer\Frontend\Sales\OrderPaymentSaveBefore" />
    </event>
</config>

Then create the app/code/Learning/CustomField/Observer/Frontend/Sales/OrderPaymentSaveBefore.php file.

Show code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php

namespace Learning\CustomField\Observer\Frontend\Sales;

use Magento\Framework\Event\Observer as EventObserver;
use Magento\Framework\Event\ObserverInterface;
use Magento\OfflinePayments\Model\Purchaseorder;
use Magento\Framework\App\Request\DataPersistorInterface;

class OrderPaymentSaveBefore implements \Magento\Framework\Event\ObserverInterface
{
    protected $order;
    protected $logger;
    protected $_serialize;
    protected $quoteRepository;

    public function __construct(
        \Magento\Sales\Api\Data\OrderInterface $order,
        \Magento\Quote\Api\CartRepositoryInterface $quoteRepository,
        \Psr\Log\LoggerInterface $logger,
        \Magento\Framework\Serialize\Serializer\Serialize $serialize
    ) {
        $this->order = $order;
        $this->quoteRepository = $quoteRepository;
        $this->logger = $logger;
        $this->_serialize = $serialize;
    }
    /**
     * Execute observer
     *
     * @param \Magento\Framework\Event\Observer $observer
     * @return void
     */
    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        $orderids = $observer->getEvent()->getOrderIds();
        if(!$orderids){
            foreach ($orderids as $orderid) {
                $order = $this->_order->load($orderid);
                $method = $order->getPayment()->getMethod();
                if($method == 'purchaseorder') {
                    $quote_id = $order->getQuoteId();
                    $quote = $this->quoteRepository->get($quote_id);
                    $paymentQuote = $quote->getPayment();
                    $paymentOrder = $order->getPayment();
                    $paymentOrder->setData('paymentpocomment',$paymentQuote->getPaymentpocomment());
                    $paymentOrder->save();
                }
            }
        }
    }
}

Step 6: Compile and deploy the module

Run the following sequence of commands to compile and deploy your custom module.

  1. Enable the new module.

    1
    
    bin/magento module:enable Learning_CustomField
    
  2. Install the new module.

    1
    
    bin/magento setup:upgrade
    
  3. Compile the code.

    1
    
    bin/magento setup:di:compile
    
  4. Deploy the static files.

    1
    
    bin/magento setup:static-content:deploy
    

Step 7: Verify that the module works

Use the following steps to verify your changes work as expected.

  1. Go to the storefront as a guest user and add a product to the cart.

  2. Go to the checkout page and select the Purchase Order payment.

  3. Verify that the Purchase Order Comment field is visible.

    Custom field in checkout page

  4. Fill the purchase order comment field in the checkout and place an order.

  5. Verify that the entered value is stored in the paymentpocomment column of the sales_order_payment table.