Writing a payment method module

There are two broad categories of payment methods enabled by the payment system out of the box: on-site payment and off-site payment. On-site payment is either processed via a third party web service using data collected through a checkout or administration form or simply entails presenting payment information to the customer for them to remit payment offline upon checkout completion. Off-site payment is enabled through a redirect from the Payment checkout page to the payment service, with customers ideally being returned back to the Payment page upon success or failure so they can be moved forward or backward in the checkout process as the case may require. Because off-site payment often requires direct customer input (such as a username and password), it is not normally possible to enact this type of payment through the administration form.

Payment gateways tend to offer multiple types of payment services, often providing on-site and off-site options. Each service should be represented by a different payment method in Drupal Commerce. Using PayPal as an example, this means the Commerce PayPal project will include modules defining their services a separate payment methods, including PayPal WPS (off-site service), WPP (on-site service), and EC (a mixture between off-site and on-site). When a payment method module is enabled, a default payment method rule will be defined that you can use to configure settings for your payment methods. During the checkout process, all the active payment method rules will be evaluated and given a chance to enable their respective payment methods for use on the checkout form. You can actually use more than one rule to enable any given payment method using a different set of API credentials or transaction settings based on conditions of the order being paid for. The important thing to ensure is that no payment method is enabled by more than one rule.

The Payment module includes functions designed to support common types of payment services, the most common being credit card payments. The file commerce_payment.credit_card.inc includes helper functions for building and validating credit card forms and data. Payment method modules integrating with credit card processing services should use these functions and can use the Commerce Authorize.Net project as an example for the integration.

Whenever a payment is attempted, a payment transaction should be created that references the order for which payment was attempted. This includes transaction attempts that failed, that require further action, or were processed successfully as reflected in the transaction's status: Failure, Pending, Success. The transaction functions as a log indicating while a payment failed or a basic receipt for successful payments. The initial amount of the transaction should be the requested payment or authorization amount, but if this changes prior to completion (such as performing a prior authorization capture for a different amount than was originally authorized), the transaction should be updated to reflect the final amount of money collected. The sum of all successful payment transactions is used to track the outstanding balance on an order.

It is quite likely that additional actions may be available to be performed on payment transactions. These include things like voiding transactions, applying a credit to the account, or performing a prior authorization capture. All such functions should be accessible from the "Operations" column of an order's Payment tab. Links appear in this column through the use of Drupal's contextual link system as demonstrated by the Commerce Authorize.Net module for prior authorization captures of authorization only transactions. Depending on the nature of the operation, you will either update the transaction (e.g. update its amount and status after capture) or create a new transaction (e.g. creating a new transaction with a negative amount to reflect a credit).

The responsibilities of a payment method module include the following main points:

  1. Defining the payment method via hook_commerce_payment_method_info(). This hook is documented in the specification and allows you to define the titles and display name for the payment method along with various callback functions used to integrate with the payment system.
  2. Defining your callback functions to add a settings form to the payment method's rules, collect the necessary information on the checkout and administrative payment forms, and accommodate the redirection process for off-site payment methods (see below).
  3. Integrating with the payment service to actually process payments, validate payment notifications, and otherwise interact with the available APIs.
  4. Defining menu items for additional payment transaction operations and providing the forms and API integration necessary to perform the operations.
  5. Regular maintenance to ensure the module remains up to date with changes in the payment service's API, changes in Drupal Commerce, and security reports. Payment method modules should be managed through drupal.org's project hosting infrastructure where they benefit from version control, issue tracking, and community feedback and security oversight.

For more information and examples of these, refer to the Payment info hook documentation and the proof-of-concept modules Commerce Authorize.Net and Commerce PayPal. Refer to the payment method FAQ for other modules that are developed or in development supporting different types of payment services and representing different countries.

Special notes for off-site payment methods

When a user is directed off-site to submit payment, their order remains in the Checkout: Payment status that by default is not designated as a shopping cart order status. This means if the user presses the browser back button to return to your site instead of the appropriate link on the page, they will arrive at an inappropriate checkout page for their order status and in fact may no longer have their order recognized by the checkout process. The order is locked down to prevent manipulations to the order in a separate tab while payment is potentially being submitted.

Additionally, most off-site payment methods will return the user to your site via a POST that includes some basic payment information, such as whether or not the payment succeeded or failed and what the actual payment amount was. You should not depend on this information to actually create and update payment transactions, though this information may be used to progress through the checkout process. This is to mitigate the risk of simulated payment completion being logged in the database as actual payments, even if the scammer ends up seeing the checkout completion page after attempting to scam the site. This is why we make the "When an order is first paid in full" event available for use alongside the "Customer completes checkout" event. Sites relying on off-site payment methods (and sites using authorization only transactions with delayed capture after the fact) should use the "When an order is first paid in full" event to perform business logic that is dependent on actual payment and not just the appearance of checkout completion.

For off-site payment methods, payment transactions should be created and updated based on secure notification from the payment gateway, such as PayPal's Instant Payment Notifications (IPN). These notifications should be properly validated according to the payment gateway's specification, and upon validation, payment transactions can be created associated with the order in question or updated as necessary.

This specification actually may result in payment notifications being processed before the customer actually returns to your site. This is especially true in the event that the customer never actually returns to your site but either closes the browser window before the payment gateway's JavaScript redirection sends them back to your site or browses to some other link on the page. As of Drupal Commerce 1.0, when you receive a payment notification and create the associated payment transaction, you should also be calling one of two API functions designed to move the related order forward or backward in the checkout process. The checkout router was updated to permit customers returning to the checkout/%commerce_order/payment page to be redirected to the appropriate checkout page upon their eventual return.

The two API functions are:

  • commerce_payment_redirect_pane_next_page($order) - call this function if your payment notification is for a successful authorization or complete payment; the order will be moved to the next checkout page, and the checkout completion event will be invoked if necessary.
  • commerce_payment_redirect_pane_previous_page($order) - call this function if the payment notification represents failed or canceled payment; the order will be moved to the previous checkout page so the customer can update try again or choose a different payment method.

To see an example of these functions implemented by an off-site payment method module, refer to the function commerce_paypal_wps_paypal_ipn_process() in the commerce_wps.module of the Commerce PayPal module.

Found errors? Think you can improve this documentation? edit this page