Creating a Custom Payment Method

This post describes the way a basic payment method can be added to Magento. It does not include any kind of payment gateway.

The code used from this post exists as a Magento module here:
https://github.com/llapgoch/magento-custom-payment-method

config.xml

Adding the following to your config.xml’s default node will, in effect create the payment type.

<default>
	<payment>
		<llapgoch_pay>
			<!-- used in determining whether to display the payment type -->
			<active>1</active>
			<title>LLAP-Goch Pay</title>
			<order_status>processing</order_status>
			<!-- the model which gets instantiated for this payment type -->
			<model>llapgoch_basicpayment/payment</model>
			<!-- This requires the billing address to be in the specified country -->
			<allowspecific>1</allowspecific>
			<specificcountry>GB,US</specificcountry>
			<!-- Only show this order type for orders with the values between the following -->
			<min_order_total>1</min_order_total>
			<max_order_total>6000</max_order_total>	   
			<message>Thank you for using LLAP-Goch Pay!</message>
    			<!-- The debug node allows us to debug the payment method -->
 			<debug>0</debug>   
		</llapgoch_pay>
	</payment>
</default>
  • all of the nodes with the exception of “message” are used automatically by Magento in determining whether the payment type should be displayed to the user.
  • order_status is the status the order will have when the order is placed.
  • allow_specific means Magento will look for the specificcountry node and validate the billing address’ country against the contents of that field.

Payment method model

<?php
class Llapgoch_BasicPayment_Model_Payment extends Mage_Payment_Model_Method_Abstract{
	// Code to match up with the groups node in default.xml
	protected $_code = "llapgoch_pay";
	// This is the block that's displayed on the checkout
	protected $_formBlockType = 'llapgoch_basicpayment/form_pay';
	// This is the block that's used to add information to the payment info in the admin and previous
	// order screens
	protected $_infoBlockType = 'llapgoch_basicpayment/info_pay';
	
	
	// Use this to set whether the payment method should be available in only certain circumstances
	// This should only allow our payment method for over two items.
	public function isAvailable($quote = null){
		if(!$quote){
			return false;
		}
		
		if($quote->getAllVisibleItems() <= 2){
			return false;
		}
		
		return true;
	}
	
	// Errors are handled as a javascript alert on the client side
	// This method gets run twice - once on the quote payment object, once on the order payment object
	// To make sure the values come across from quote payment to order payment, use the config node sales_convert_quote_payment
    public function validate(){
       parent::validate();
	   
	   // This returns Mage_Sales_Model_Quote_Payment, or the Mage_Sales_Model_Order_Payment
       $info = $this->getInfoInstance();

       $no = $info->getCheckNo();
       $date = $info->getCheckDate();
	   
       if(empty($no) || empty($date)){
           Mage::throwException($this->_getHelper()->__('Check No and Date are required fields'));
       }
	   
	   if(strlen($no) < 5){
		   Mage::throwException($this->_getHelper()->__('Number must be five or more characters'));
	   }
       return $this;
   }
	   
}
  • This class is automatically instantiated by the model node defined the config.xml above
  • php $_code = "llapgoch_pay" tells the class where to find its config node in the xml, this is also used in our template file to append to the container’s ID which is what Magento will use to hide and display the form.
  • Config values set for the payment type can be retrieved using the method getConfigData($node)
  • php $_formBlockType = 'llapgoch_basicpayment/form_pay'; tells the class which block to use for the checkout form. When in the instance of the block, we can get the payment method object using $this->getMethod() which is automatically set on the block. If we need to get config data in the block or template, we can use $this->getMethod()->getConfigData($node)
  • php $_infoBlockType = 'llapgoch_basicpayment/info_pay'; This defines a block class to be used when displaying the payment information either in the admin area or on the front end in customer accounts. It doesn’t behave like a normal block class per se, it just sets data to be output.
  • The method isAvailable is used by Magento in determining whether this payment method should be used
  • The validate method is used to validate whether all requirements have been met and the user can proceed. Commonly, any form fields will be validated here. To do this, we get an instance of a subclass of Mage_Payment_Model_Info, which will typically be Mage_Sales_Model_Quote_Payment or Mage_Sales_Model_Order_Payment, as the validate method is run once for each. The quote payment object will have the values set on it from the form automatically, however for that data to persist to the order payment, we have to create new columns in the sales_flat_order_payment table and set the fields to be copied over via XML (More on this later).
  • As the onepage form loads its content in via AJAX, any exceptions which are thrown within this context are output as JSON and alerted to the user as a Javascript alert.

Copying the new fields to the sales payment object

As the new values will automatically be set on the quote payment object, and hence pass the first validation routine, we need to tell Magento to copy these values to the order payment object. This stage only provides the mechanism to copy the values specified, just doing this won’t make them persist to the sales_flat_order_payment table. The following needs to be added to the module’s config.xml:

<config>
	<global>
		<fieldsets>
			<sales_convert_quote_payment>
				<check_no>
					<to_order_payment>*</to_order_payment>
				</check_no>
				<check_date>
					<to_order_payment>*</to_order_payment>
				</check_date>
			</sales_convert_quote_payment>
		</fieldsets>
    </global>
</config>
  • sales_convert_quote_payment is a node that’s looked for in the Mage_Sales_Model_Convert_Quote object in the paymentToOrderPayment method.
  • It uses the copyFieldset method of the Mage::helper(‘core’) helper.
  • Each of the child nodes of sales_convert_quote_payment are converted onto the order payment object, providing they have the node to_order_payment.
  • The value will be converted to a value with the same name if the node value of to_order_payment is an asterisk, otherwise it will adopt the value of the node.

The Payment Block Class

<?php 
class Llapgoch_BasicPayment_Block_Form_Pay extends Mage_Core_Block_Template{
	protected function _construct(){
		parent::_construct();
		
		$this->setTemplate('llapgoch/basicpayment/form/pay.phtml');
	}
}
  • A basic block class which just sets its own template

The Payment Block Template

<?php
// The ID using the code on the UL is important - this is what Magento will use to hide and display the 
// Form with javascript - it should be set to display:none by default
?>
<?php $_code = $this->getMethod()->getCode() ?>
<ul class="form-list" id="payment_form_<?php echo $_code ?>" style="display:none;">
    <li>
        <label for="<?php echo $_code ?>_check_no" class="required"><em>*</em><?php echo $this->__('Check No#') ?></label>
        <span class="input-box">
            <input type="text" title="<?php echo $this->__('Check No#') ?>" class="input-text required-entry" id="<?php echo $_code ?>_check_no" name="payment[check_no]" value="<?php echo $this->htmlEscape($this->getInfoData('check_no')) ?>" />
        </span>
    </li>
    <li>
        <label for="<?php echo $_code ?>_check_date" class="required"><em>*</em><?php echo $this->__('Check Date:') ?></label>
        <span class="input-box">
            <input type="text" title="<?php echo $this->__('Check Date:') ?>" class="input-text required-entry" id="<?php echo $_code ?>_check_date" name="payment[check_date]" value="<?php echo $this->htmlEscape($this->getInfoData('check_date')) ?>" />
        </span>
    </li>
	<li>
		<div>
		    <?php echo $this->getMethod()->getConfigData('message');?>
		</div>
	</li>
</ul>

  • Magento uses the code set in the payment class to show and hide the form with javascript
  • The form should be set to display:none initially.
  • The value of all submitted form elements will be placed automatically on the Quote Payment object which can then be used in the validate method of the Payment class. All form data should be placed in the ‘payment’ array, E.g. payment[check_date].
  • “Message” is an arbitrary node set in the config.xml. Any nodes can be retrieved using the getConfigData method.

The Info Block

This is the block that’s used to add data to the payment info blocks which exist in the admin area and the customer’s previous order page.

<?php
// This block allows data along with the payment method to be presented on the admin screen and user order screen.
class Llapgoch_BasicPayment_Block_Info_Pay extends Mage_Payment_Block_Info{
    protected function _prepareSpecificInformation($transport = null)
       {
           if (null !== $this->_paymentSpecificInformation) {
               return $this->_paymentSpecificInformation;
           }
           $info = $this->getInfo();
           $transport = new Varien_Object();
           
           $transport->addData(array(
               Mage::helper('payment')->__('Check No#') => $info->getCheckNo(),
               Mage::helper('payment')->__('Check Date') => $info->getCheckDate()
           ));
		   
           $transport = parent::_prepareSpecificInformation($transport);
           return $transport;
       }
}
  • First we check if the value of paymentSpecificInformation has been set. This is set by the Mage_Payment_Block_Info superclass’s _prepareSpecificInformation method.
  • The data we require to be displayed gets added to the transport object.
  • We translate the labels of the labels to be displayed at this point.

The Setup Class

This is required to add our required information to the sales_flat_order_payment table. If these fields don’t exist then the validation will pass in the Payment class above, as they’ll get copied to the sales payment object from the quote payment object, but they won’t persist into the database.

<?php
$this->startSetup();

// We have to create these columns so that our new fields show up in the sales_flat_order_payment table
// We don't need to create these columns for the quote payment table as the data is copied from the form
// To the quote object in code - it's then converted to an order payment object, however it may be required to add these columns to the quote so that the quote reloads with the data. To be investigated!
$this->getConnection()->addColumn(
	// getTable returns the name of the table as a string
	$this->getTable('sales/order_payment'),
	'check_no',
	array(
		// Use TYPE_TEXT instead of TYPE_VARCHAR, as it's deprecated and will throw an error
		// Adding a length will make it as varchar
		'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
		'nullable' => true,
		'default' => null,
		// Comment must be provided
		'comment' => 'Check Number',
		'length' => 100			
	)
);

$this->getConnection()->addColumn(
	$this->getTable('sales/order_payment'),
	'check_date',
	array(
		'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
		'nullable' => true,
		'default' => null,
		'comment' => 'Check Date',
		'length' => 255
	)
);

$this->endSetup();
  • Remember to use TYPE_TEXT with a length set instead of TYPE_VARCHAR, as this has been deprecated. Using varchar will throw an exception.
  • A comment must be provided for each new column. An exception will be thrown if not.

Alternate Setup Class

There is a utility function inside of the **Mage_Sales_Model_Resource_Setup ** class to aid with adding columns to the tables above:

<?php
   $this->startSetup();
    $installer = new Mage_Sales_Model_Resource_Setup('core_setup');

    $options = array(
        'type'     => Varien_Db_Ddl_Table::TYPE_INTEGER,
        'visible'  => true,
        'required' => false
    );

    $installer->addAttribute('order_payment', 'check_date', $options);
    $installer->addAttribute('order_payment', 'check_no', $options);
}

$this->endSetup();

– This essentially runs the same code as the previous example, but with a more succinct installer.

system.xml

Just for completeness, this allows the values in default to be overridden from the admin area.

<?xml version="1.0"?>
<config>
	<sections>
		<payment>
			<groups>
				<llapgoch_pay translate="label" module="llapgoch_basicpayment">
					<label>LLAP-Goch Basic Payment</label>
					<sort_order>300</sort_order>
					<show_in_default>1</show_in_default>
					<show_in_website>1</show_in_website>
					<show_in_store>1</show_in_store>
					<fields>
						<active translate="label" module="llapgoch_basicpayment">
							<label>Enabled</label>
							<sort_order>1</sort_order>
							<show_in_default>1</show_in_default>
							<show_in_website>1</show_in_website>
							<show_in_store>1</show_in_store>
							<frontend_type>select</frontend_type>
							<source_model>adminhtml/system_config_source_yesno</source_model>
						</active>
						
						<order_status translate="label" module="llapgoch_basicpayment">
							<label>Order Status</label>
							<sort_order>10</sort_order>
							<show_in_default>1</show_in_default>
							<show_in_website>1</show_in_website>
							<show_in_store>1</show_in_store>
							<frontend_type>select</frontend_type>
							<source_model>adminhtml/system_config_source_order_status_newprocessing</source_model>
						</order_status>
						
						<title translate="label" module="llapgoch_basicpayment">
							<label>Title</label>
							<sort_order>20</sort_order>
							<show_in_default>1</show_in_default>
							<show_in_website>1</show_in_website>
							<show_in_store>1</show_in_store>
							<frontend_type>text</frontend_type>
						</title>
					</fields>
				</llapgoch_pay>
			</groups>
		</payment>
	</sections>
</config>

The code used from this post exists as a Magento module here:
https://github.com/llapgoch/magento-custom-payment-method