Add Customer Attributes Programmatically in Magento 2

In this article we’ll see what’re customer attributes and how we add them programmatically in Magento 2. So we’ll proceed with following points :

1. What’re customer attributes
2. Why we need to add them programmatically
3. Steps to create customer attribute.

1. What’re customer attributes

In Magento, fields which are used in registration, login and filling customer address are basically customer attributes. They’re further divided into customer attributes and customer address attributes. Now if attributes used in customer registration / login are customer attributes and attribute used in address information for customers are customer address attributes. Some examples of customer attributes are below :

a. Customer Attributes
– First Name, Last Name, DOB etc
b. Customer Address Attributes
– Zip, City, Street, Country, Region/State etc

2. Why we need to add them programmatically

Magento by default didn’t provide functionality to add customer attributes in CE and sometime our project requirement have to add some extra fields(customer attributes) in registration form or address form. To do so we need to add them programmatically or through third party modules.

3. Steps to create customer attribute.

Let see how we can create customer attributes programmatically in Magento 2 step by step :-

Step 1: Create setup file InstallData.php

Firstly, we will create the InstallData.php file:
File: Excellence/Customerattr/Setup/InstallData.php

<?php
namespace Excellence\Customerattr\Setup;

use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

class InstallData implements InstallDataInterface
{
	private $eavSetupFactory;

	public function __construct(EavSetupFactory $eavSetupFactory)
	{
		$this->eavSetupFactory = $eavSetupFactory;
	}

}

In this class, we define the EAV setup model which will be use to interact with Magento 2 attribute.

Step 2: Define the install() method

After that, we have to define the install() method and create eav setup model:

	
public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
	{
		$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
		$eavSetup->addAttribute(
			\Magento\Customer\Model\Customer::ENTITY,
			'gst_number',
			[
				'type'         => 'varchar',
				'label'        => 'GST Number',
				'input'        => 'text',
				'required'     => false,
				'visible'      => true,
				'user_defined' => true,
				'position'     => 100,
				'system'       => 0,
			]
		);
}

Step 3: Create custom attribute

Finally, we need to set the forms in which the attributes will be used. In this step, we need define the eavConfig object which allow us to call the attribute back and set the data for it and the full code to create customer attribute is:

Now our final InstallData.php looks like below :-

<?php

namespace Excellence\Customerattr\Setup;

use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Eav\Model\Config;
use Magento\Customer\Model\Customer;

class InstallData implements InstallDataInterface
{
	private $eavSetupFactory;

	public function __construct(EavSetupFactory $eavSetupFactory, Config $eavConfig)
	{
		$this->eavSetupFactory = $eavSetupFactory;
		$this->eavConfig       = $eavConfig;
	}

	public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
	{
		$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
		$eavSetup->addAttribute(
			\Magento\Customer\Model\Customer::ENTITY,
			'gst_number',
			[
				'type'         => 'varchar',
				'label'        => 'GST Number',
				'input'        => 'text',
				'required'     => false,
				'visible'      => true,
				'user_defined' => true,
				'position'     => 999,
				'system'       => 0,
			]
		);
		$attribute = $this->eavConfig->getAttribute(Customer::ENTITY, 'gst_number');

		//  used_in_forms are of these types you can use forms key according to your need ['adminhtml_checkout','adminhtml_customer','adminhtml_customer_address','customer_account_edit','customer_address_edit','customer_register_address', 'customer_account_create']
		
               $attribute->setData(
			'used_in_forms',
			['adminhtml_customer', 'customer_account_create']

		);
		$attribute->save();
	}
}

Step 4: Show Customer Attribute in Register form
For showing this customer attribute on the registration page, we need to do following things :

We will add our phtml files to ‘form.additional.info’ reference name using Excellence/Customerattr/view/frontend/layout/customer_account_create.xml

<page 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="form.additional.info">
            <block class="Magento\Framework\View\Element\Template" name="mobile_number" template="Excellence_MobileLogin::mobile.phtml"/>
        </referenceContainer>
    </body>
</page>

Explaination : we’re not overriding the entire vendor/magento/module-customer/view/frontend/templates/form/register.phtml file. Instead we used container name form.additional.info as reference and adding a new block to it. ‘form.additional.info’ is being called in register.phtml using this code getChildHtml(‘form_additional_info’); ?>

So by above xml we can simply add new attribute fields with editing register.phtml of core or without overriding it and getChildHtml(‘form_additional_info’); ?> automatically read content of ‘form.additional.info’ child block.

That’s all for adding customer attribute programmatically.

Creating Custom Online Payment Method in Magento 2

In this blog, we will learn to create online payment method. We will implement Stripe payment as a reference. You may change it as per your requirement. It will depend upon the library that the gateway provider gives.

Stripe Payment Gateway has several features, but we will be implementing the most basic ones. We will use the official PHP library of stripe i.e. https://github.com/stripe/stripe-php

It should be noticed that the library of Stripe is managed via Composer so it can be installed via that, alternatively, it can be downloaded and the files should be put in vendor/stripe. If we manage the whole process via composer, then it can be automatically included in the code through autoloader. But if we don’t, then we will have to include it in our code.

We will be creating several files and most of the files will be same as the previous blog. So, we will be dicussing about the important files only. Now, let’s get started:

The structure of the extension will look like this:

1. System.xml

This file is used to create the store configuration part for the extension so that the configuration for setting up the payment method can be done from admin panel easily. We will define several fields like API Key, Supported Credit Card Types etc.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="payment">
            <group id="excellence_stripe" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Stripe</label>
                <comment>
                    <![CDATA[<a href="https://stripe.com/" target="_blank">Click here to sign up for Stripe account</a>]]>
                </comment>
                <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Enabled</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="title" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Title</label>
                </field>
                <field id="api_key" translate="label" type="obscure" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Api Key</label>
                    <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model>
                </field>
                <field id="debug" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Debug</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="cctypes" translate="label" type="multiselect" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Credit Card Types</label>
                    <source_model>Excellence\Stripe\Model\Source\Cctype</source_model>
                </field>
                <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Sort Order</label>
                </field>
                <field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Payment from Applicable Countries</label>
                    <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model>
                </field>
                <field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Payment from Specific Countries</label>
                    <source_model>Magento\Directory\Model\Config\Source\Country</source_model>
                </field>
                <field id="min_order_total" translate="label" type="text" sortOrder="98" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Minimum Order Total</label>
                </field>
                <field id="max_order_total" translate="label" type="text" sortOrder="99" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Maximum Order Total</label>
                    <comment>Leave empty to disable limit</comment>
                </field>
            </group>
        </section>


    </system>
</config>

2. Model

We will create a Model for handling the processing of payment and will define this model is the config.xml as explained in the previous blog:

<?php namespace Excellence\Stripe\Model; class Payment extends \Magento\Payment\Model\Method\Cc {              const CODE = 'excellence_stripe';         protected $_code = self::CODE;         protected $_isGateway = true;         protected $_canCapture = true;              protected $_canCapturePartial = true;              protected $_canRefund = true;         protected $_canRefundInvoicePartial = true;         protected $_stripeApi = false;              protected $_countryFactory;         protected $_minAmount = null;         protected $_maxAmount = null;         protected $_supportedCurrencyCodes = array('USD');         protected $_debugReplacePrivateDataKeys = ['number', 'exp_month', 'exp_year', 'cvc'];                  public function __construct(                 \Magento\Framework\Model\Context $context,                 \Magento\Framework\Registry $registry,                 \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory,                 \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory,                 \Magento\Payment\Helper\Data $paymentData,                 \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,                 \Magento\Payment\Model\Method\Logger $logger,                 \Magento\Framework\Module\ModuleListInterface $moduleList,                 \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,                 \Magento\Directory\Model\CountryFactory $countryFactory,                 \Stripe\Stripe $stripe,                 array $data = array()         ) {                   parent::__construct($context,$registry,$extensionFactory,$customAttributeFactory,$paymentData,$scopeConfig,$logger,$moduleList,$localeDate,null,null,$data);                   $this->_countryFactory = $countryFactory;

         $this->_stripeApi = $stripe;        $this->_stripeApi->setApiKey(
            $this->getConfigData('api_key')
         );

         $this->_minAmount = $this->getConfigData('min_order_total');
         $this->_maxAmount = $this->getConfigData('max_order_total');
    }

    /**
     * Payment capturing
     *
     * @param \Magento\Payment\Model\InfoInterface $payment
     * @param float $amount
     * @return $this
     * @throws \Magento\Framework\Validator\Exception
     */
    public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount)
    {
        //throw new \Magento\Framework\Validator\Exception(__('Inside Stripe, throwing donuts :]'));

        /** @var \Magento\Sales\Model\Order $order */
        $order = $payment->getOrder();

        /** @var \Magento\Sales\Model\Order\Address $billing */
        $billing = $order->getBillingAddress();

        try {
            $requestData = [
                'amount'        => $amount * 100,
                'currency'      => strtolower($order->getBaseCurrencyCode()),
                'description'   => sprintf('#%s, %s', $order->getIncrementId(), $order->getCustomerEmail()),
                'card'          => [
                    'number'            => $payment->getCcNumber(),
                    'exp_month'         => sprintf('%02d',$payment->getCcExpMonth()),
                    'exp_year'          => $payment->getCcExpYear(),
                    'cvc'               => $payment->getCcCid(),
                    'name'              => $billing->getName(),
                    'address_line1'     => $billing->getStreetLine(1),
                    'address_line2'     => $billing->getStreetLine(2),
                    'address_city'      => $billing->getCity(),
                    'address_zip'       => $billing->getPostcode(),
                    'address_state'     => $billing->getRegion(),
                    'address_country'   => $billing->getCountryId(),
                    // To get full localized country name, use this instead:
                    // 'address_country'   => $this->_countryFactory->create()->loadByCode($billing->getCountryId())->getName(),
                ]
            ];

            $charge = \Stripe\Charge::create($requestData);
            $payment
                ->setTransactionId($charge->id)
                ->setIsTransactionClosed(0);

        } catch (\Exception $e) {
            $this->debugData(['request' => $requestData, 'exception' => $e->getMessage()]);
            $this->_logger->error(__('Payment capturing error.'));
            throw new \Magento\Framework\Validator\Exception(__('Payment capturing error.'));
        }

        return $this;
    }

    /**
     * Payment refund
     *
     * @param \Magento\Payment\Model\InfoInterface $payment
     * @param float $amount
     * @return $this
     * @throws \Magento\Framework\Validator\Exception
     */
    public function refund(\Magento\Payment\Model\InfoInterface $payment, $amount)
    {
        $transactionId = $payment->getParentTransactionId();

        try {
            \Stripe\Charge::retrieve($transactionId)->refund(['amount' => $amount * 100]);
        } catch (\Exception $e) {
            $this->debugData(['transaction_id' => $transactionId, 'exception' => $e->getMessage()]);
            $this->_logger->error(__('Payment refunding error.'));
            throw new \Magento\Framework\Validator\Exception(__('Payment refunding error.'));
        }

        $payment
            ->setTransactionId($transactionId . '-' . \Magento\Sales\Model\Order\Payment\Transaction::TYPE_REFUND)
            ->setParentTransactionId($transactionId)
            ->setIsTransactionClosed(1)
            ->setShouldCloseParentTransaction(1);

        return $this;
    }

    /**
     * Determine method availability based on quote amount and config data
     *
     * @param \Magento\Quote\Api\Data\CartInterface|null $quote
     * @return bool
     */
    public function isAvailable(\Magento\Quote\Api\Data\CartInterface $quote = null)
    {
        if ($quote && (
            $quote->getBaseGrandTotal() < $this->_minAmount
            || ($this->_maxAmount && $quote->getBaseGrandTotal() > $this->_maxAmount))
        ) {
            return false;
        }

        if (!$this->getConfigData('api_key')) {
            return false;
        }

        return parent::isAvailable($quote);
    }

    /**
     * Availability for currency
     *
     * @param string $currencyCode
     * @return bool
     */
    public function canUseForCurrency($currencyCode)
    {
        if (!in_array($currencyCode, $this->_supportedCurrencyCodes)) {
            return false;
        }
        return true;
    }
}

In this file, there are two main methods:

  1. capture(): This method is handling the process of charging the credit card and saving the transaction. We are just fetching the billing address details from payment object, and the passing it to stripe API along with card details and order amount. The API will process this data and will return a charge object. We are just saving its id as transaction id for the order.
  2. refund(): This method will be used to process the refund. The previously saved transaction id will be sent to the API and then refund will be processed.

You can see the exact files and directory structure over here.

Creating Custom Payment Method in Magento 2

Out of the box, Magento comes with several payment methods such as Paypal, Braintree, COD, Bank transfer etc. But we might need to create our own custom payment method to meet any specific need to business. generally, there are two types of payment methods:

  1. Offline Payment Method
  2. Online Payment Method

In this blog, we will learn about creating an offline payment method.

We will need to create a custom module with following files:

1. create Excellence/Payment/registration.php (this will be used to register your module)

<?php

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Excellence_Payment',
    __DIR__
);

2. Create Excellence/Payment/etc/module.xml (this will be used to define the module)

<?xml version="1.0"?>
<!--
/**
* @copyright Copyright (c) 2015 X.commerce, Inc. (http://www.magentocommerce.com)
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
 <module name="Excellence_Payment" setup_version="1.0.0">
 </module>
</config>

3. Create Excellence/Payment/etc/config.xml for define your payment method.

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../Store/etc/config.xsd">
    <default>
        <payment>
                <paymentmethod>
                <active>1</active>
                <title>PaymentMethod</title>
                <order_status>pending_payment</order_status>
                <instructions>Instruction.</instructions>
                <payment_action>true</payment_action>
                <model>Excellence\Payment\Model\PaymentMethod</model>
                <group>offline</group>
            </paymentmethod>
            <!-- payment-config -->
        </payment>
    </default>
</config>

4. Now we will define the system configuration using Excellence/Payment/etc/adminhtml/system.xml, so that admin can handle the configuration of the payment method

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../Config/etc/system_file.xsd">
    <system>
        <section id="payment">
            <group id="paymentmethod" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Payment</label>
                <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Enabled</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="title" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Title</label>
                </field>
                <field id="order_status" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>New Order Status</label>
                    <source_model>Excellence\Payment\Model\Config\Source\Order\Status\Pendingpayment</source_model>
                </field>
              
                <field id="allowspecific" translate="label" type="allowspecific" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Payment from Applicable Countries</label>
                    <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model>
                </field>
                <field id="specificcountry" translate="label" type="multiselect" sortOrder="41" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Payment from Specific Countries</label>
                    <source_model>Magento\Directory\Model\Config\Source\Country</source_model>
                    <can_be_empty>1</can_be_empty>
                </field>
                <field id="instructions" translate="label" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Instructions</label>
                </field>
                <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Sort Order</label>
                    <frontend_class>validate-number</frontend_class>
                </field>
            </group>
            <!-- payment-group -->
        </section>
    </system>
</config>

5. Now create the model file that we had defined in config.xml at Excellence/Payment/Model/PaymentMethod.php

<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Excellence\Payment\Model;

/**
 * Pay In Store payment method model
 */
class PaymentMethod extends \Magento\Payment\Model\Method\AbstractMethod
{

    /**
     * Payment code
     *
     * @var string
     */
    protected $_code = 'paymentmethod';

    /**
     * Availability option
     *
     * @var bool
     */
    protected $_isOffline = true;
}

Now we are done with the basic files and configurations, now we will show the payment method on the frontend.

6. Create Excellence/Payment/view/frontend/web/js/view/payment/paymentmethod.js (it will be used to register the template and render it)

/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
/*browser:true*/
/*global define*/
define(
    [
        'uiComponent',
        'Magento_Checkout/js/model/payment/renderer-list'
    ],
    function (
        Component,
        rendererList
    ) {
        'use strict';
        rendererList.push(
            {
                type: 'paymentmethod',
                component: 'Excellence_Payment/js/view/payment/method-renderer/paymentmethod-method'
            }
        );
        /** Add view logic here if needed */
        return Component.extend({});
    }
);

7. Create Excellence/Payment/view/frontend/web/js/view/payment/method-renderer/paymentmethod-method.js

/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
/*browser:true*/
/*global define*/
define(
    [
        'Magento_Checkout/js/view/payment/default'
    ],
    function(Component) {
        'use strict';

        return Component.extend({
            defaults: {
                template: 'Excellence_Payment/payment/paymentmethod'
            },

            /** Returns send check to info */
            getMailingAddress: function() {
                return window.checkoutConfig.payment.checkmo.mailingAddress;
            },


        });
    }
);

8. Now create the template file at Excellence/Payment/view/frontend/web/template/payment/paymentmethod.html

<!--
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<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>
        <div class="checkout-agreements-block">
            <!-- ko foreach: $parent.getRegion('before-place-order') -->
                <!-- ko template: getTemplate() --><!-- /ko -->
            <!--/ko-->
        </div>
        <div class="actions-toolbar">
            <div class="primary">
                <button class="action primary checkout"
                        type="submit"
                        data-bind="
                        click: placeOrder,
                        attr: {title: $t('Place Order')},
                        css: {disabled: !isPlaceOrderActionAllowed()},
                        enable: (getCode() == isChecked())
                        "
                        disabled>
                    <span data-bind="i18n: 'Place Order'"></span>
                </button>
            </div>
        </div>
    </div>
</div>

9. Now create Excellence/Payment/view/frontend/layout/checkout_index_index.xml for adding the payment method to checkout page.

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="checkout.root">
            <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="checkout" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="steps" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="billing-step" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="payment" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="renders" xsi:type="array">
                                                            <item name="children" xsi:type="array">
                                                            <!-- merge payment method renders here -->
                                                                <item name="payment-paymentmethod" xsi:type="array">
                                                                    <item name="component" xsi:type="string">Excellence_Payment/js/view/payment/paymentmethod</item>
                                                                    <item name="methods" xsi:type="array">
                                                                  
                                                                         <item name="paymentmethod" xsi:type="array">
                                                                            <item name="isBillingAddressRequired" xsi:type="boolean">true</item>
                                                                        </item>
                                                                    </item>
                                                                </item>
                                                            <!-- item-renderer -->
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

That’s all folks.

In next blog, we will learn to create online payment method.

Displaying Custom Product Attribute on Product Page

Whenever we create a custom product attribute, we might need to show it on the product page. It can be shown in two forms:

  1. In existing product tab (in More Information tab)
  2. In new tab (by adding a new one)

1. In Existing Product Tab

To show an attribute in More Information tab, you need to set Visible on Catalog Pages on Storefront to yes for the attribute.

After this, you will be able to see the attribute as shown here:

2. In new tab

Sometime we might need to add a new tab and show the attribute value in that (mainly for the attributes of type Text Area or if any customization is required in how the value is getting shown). In this case, we need a custom module or theme. Just follow these basic steps:

Note: We will be using an attribute with name Test Attribute and code test_attribute for reference. You may do the modification based on your attribute

  1. Create app/code/Vendor/ModuleName/view/frontend/layout/catalog_product_view.xml or app/design/frontend/Vendor/ThemeName/Magento_Catalog/layout/catalog_product_view.xml as per your requirement
  2. Then add this code to the file:
    <?xml version="1.0"?>
    <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
     <body>
     <referenceBlock name="product.info.details">
     <block class="Magento\Catalog\Block\Product\View\Description" name="product.test_attribute" template="product/view/attribute.phtml" group="detailed_info">
     <arguments>
     <argument name="at_call" xsi:type="string">getTestAttribute</argument>
     <argument name="at_code" xsi:type="string">test_attribute</argument>
     <argument name="css_class" xsi:type="string">test_attribute</argument>
     <argument name="at_label" xsi:type="string">none</argument>
     <argument name="title" translate="true" xsi:type="string">Test Attribute</argument>
     </arguments>
     </block>
     </referenceBlock>
     </body>
    </page>
    
  3. It should be noted that we are using method call getTestAttribute as the attribute code is test_attribute. Modify it as per the requirement
  4. Save the file and that’s it. The attribute will be shown on Product page in new tab as shown here:

Extending Parent Styles in Custom Theme using Less

Extending Parent Styles in Custom Theme using Less

To extend the parent theme’s styles in your theme:

  1. Create a _extend.less file there. The path to it looks like following:
    <theme_dir>/
    │ ├── web/
    │ │ ├── css/
    │ │ │ ├── source/
    │ │ │ ├──_extend.less
    
  2. Add your LESS code in this file.

Extending a theme using _extend.less is the simplest option when you are happy with everything the parent theme has, but want to add more styles.

Overriding Parent Styles in Custom Theme using Less

To override parent styles (that is, override default Magento UI library variables):

  1. In your theme directory, create a web/css/source sub-directory.
  2. Create a _theme.less file here. The path to it then looks like following:
    <theme_dir>/
    │ ├── web/
    │ │ ├── css/
    │ │ │ ├── source/
    │ │ │ ├──_theme.less
    ...
    
  3. It is important to remember that your _theme.less override the parent _theme.less.
  4. Copy all variables you need from the parent _theme.less, including those which will not be changed. For example, if your theme inherits from Blank, the _theme.less you should copy from is located at <Magento_Blank_theme_dir>/web/css/source/_theme.less
  5. Make the necessary changes.
  6. It might be possible that the parent theme doesn’t have _theme.less file. It’s just for a reference. If there is a file with name _module.less in <Magento_Blank_theme_dir>/Magento_Theme/web/css/source then you need to copy the same file in your theme at similar location  <theme_dir>/Magento_Theme/web/css/source and then make required changes into this file

The drawback of this approach is that you need to monitor and manually update your files whenever the parent’s _theme.less is updated.

Note: To see the changes that you had made in the less file, you need to perform setup:upgrade and setup:static-content:deploy.

Less Compilation with Grunt

Prerequisites

Make sure that you set your Magento application to the developer or default mode.

Installing and configuring Grunt

Magento has built-in Grunt tasks configured, but there are still several prerequisite steps you need to take to be able to use it:

  1. Install node.js to any location on your machine. Refer: https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions
  2. Install Grunt CLI tool globally. To do this, run the following command in a command prompt:
    npm install -g grunt-cli
  3. Now Rename the following files:
    Gruntfile.js.sample to Gruntfile.js
    package.json.sample to package.json
  4. Install (or refresh) the node.js project dependency, including Grunt, for your Magento instance. To do this, run the following commands in a command prompt:
    cd <your_Magento_root_directory>
    npm install
    npm update
    
  5. Add your theme to Grunt configuration. To do this, in the dev/tools/grunt/configs/themes.js file, add your theme to module.exports like following:
    module.exports = {
     ...
     <theme>: {
     area: 'frontend',
     name: '<Vendor>/<theme>',
     locale: '<language>', 
     files: [
     '<path_to_file1>', //path to root source file
     '<path_to_file2>'
     ],
     dsl: 'less'
     ...
    },
    

    Where the following notation is used:

    <theme>: your theme code, conventionally should correspond to the theme directory name.

    <language>: specified in the ‘code_subtag’ format, for example en_US. Only one locale can be specified here. To debug the theme with another locale, create one more theme declaration, having specified another value for language

    <path_to_file>: path to the root source file, relative to the app/design/frontend/<Vendor>/<theme/>web directory. You need to specify all root source files of the theme. If your theme inherits from a certain theme, and does not contain its own root source files, specify the root source files of the parent theme.

  6. (Optional) If you want to use Grunt for “watching” changes automatically, without reloading pages in a browser each time, install the LiveReload extension in your browser.
Grunt Commands
Command Usage
grunt clean:<theme>

For example:

grunt clean:blank

It removes the theme related static files in the pub/static and var directories.
grunt exec:<theme> It republishes symlinks to the source files to the pub/static/frontend/<Vendor>/<theme>/<locale> directory.
grunt less:<theme> Compiles .css files using the symlinks published in the pub/static/frontend/<Vendor>/<theme>/<locale> directory
grunt watch Tracks the changes in the source files, recompiles .css files, and reloads the page in the browser pages (you need to have LiveReload installed for you browser)

Basically, you need to use grunt less:<theme> which would compile less.