Payment Gateway API

Restrict Content Pro includes a complete payment gateway API that permits developers to integrate Restrict Content Pro with additional payment processors.

Building a payment gateway includes two primary components:

  1. Registering the gateway
  2. Providing an extension of the RCP_Payment_Gateway class

Registering a gateway

Payment gateways are registered through the  rcp_payment_gateways filter. This receives one parameter, $gateways, that is an array of all currently registered gateways.

Each gateway in the array is an array with the following keys:

  • label - The payment method label shown to customers
  • admin_label - The label used to describe the gateway in the settings screen
  • class - The name of the class that extensions the RCP_Payment_Gateway base class

The ID of the gateway is determined by the key in the main array.

Registering a gateway looks like this:

Once you have registered the gateway, you will write out the gateway class. The class provided should be an extension of the base payment gateway class, which looks like this:

There are several foundational methods provided by the base class:

  • init() - (required) This is used to setup a few gateway properties, such as API keys and endpoints.
  • process_signup() - (required) This is the method that is run when the registration form is submitted and all validation passes. This is used to actually process the payment and/or set up the gateway subscription.
  • process_webhooks() - (optional) This is the method that is used to detect webhooks from the payment processor. This is usually used for processing renewals.
  • scripts() - (optional) This method allows you to enqueue or output any JS/CSS needed for the payment gateway.
  • fields() - (optional) This method allows you to output gateway-specific fields on the registration form.
  • validate_fields() - (optional) This method allows you to validate the custom fields you have added to the registration form for the gateway.

A gateway is only required to provide the init() and process_signup() methods, but the other methods will likely be used as well for many payment gateways.

Initializing the class

The init() method is primarily used for declaring which features the gateway supports and checking for sandbox mode. A common init() method looks like this:

In this particular example, support is declared for one-time payments, recurring payments, signup fees, and free trials. The API keys for both test and live modes are loaded into the class properties, and the gateway SDK file is included.

Processing signup

The process_signup() method is where most of magic happens. This is the method that handles calling the payment processor's API or redirecting to an external payment page. For this example, let's assume we are going to redirect to an external payment page.

Before adding our processing logic to process_signup(), our class looks something like this:

We now write our redirection logic in process_signup():

Here's a list of important things to do in process_signup():

  1. If $this->auto_renew is FALSE - process a one-time transaction using $this->initial_amount.
  2. If $this->auto_renew is TRUE - set up a recurring subscription at the payment gateway. The amount charged today should be $this->initial_amount. The amount charged on renewals should be $this->amount. Once the subscription is set up, store the gateway's subscription ID in the membership using:
    $this->membership->set_gateway_subscription_id( 'gateway_subscription_id_goes_here' );
    	
  3. Once the initial payment is confirmed as paid, update the pending payment record to set the 'transaction_id' and change the status to 'complete' like so:
    $rcp_payments_db->update( $this->payment->id, array(
    	'transaction_id' => 'your_transaction_id_here', // @todo set transaction ID
    	'status'         => 'complete'
    ) );
    	

Here's a list of useful properties in the base RCP_Payment_Gateway class that you may use:

  • $supports - Array of supported features.
  • $email - Customer's email address.
  • $user_id - Customer's user account ID.
  • $user_name  - Customer's username.
  • $currency - The selected currency code (i.e. "USD").
  • $amount - Recurring subscription amount. This excludes any one-time fees or one-time discounts.
  • $initial_amount - This is the amount to be billed for the first payment. It includes any one-time setup fees, one-time discounts, and credits.
  • $discount - Total amount discounted from the payment.
  • $length - Membership duration.
  • $length_unit - Membership duration unit (day, month, or year).
  • $signup_fee - Signup fees to apply to the first payment. This number is included in $initial_amount.
  • $subscription_key - Unique subscription key for this membership.
  • $subscription_id - The membership level ID number the customer is signing up for.
  • $subscription_name - The name of the membership level the customer is signing up for.
  • $auto_renew - Whether or not this registration is for a recurring subscription.
  • $return_url - URL to redirect the customer to after a successful registration.
  • $test_mode - Whether or not the site is in sandbox mode.
  • $payment - Payment object for this transaction. Going into the gateway this has the status "pending". It should be updated to "complete" after successful payment is made.
  • $customer - RCP_Customer object for this user.
  • $membership - RCP_Membership object for the pending membership record.
  • $subscription_start_date - Start date of the subscription in MySQL format. It starts today by default (empty string). If a customer's first payment is free (such as for a free trial or 100% off one-time discount) then this value will be set to the date the first payment should be made.

Processing the signup is a major part of building a payment gateway, but there are several other components as well, which we will continue below.

Adding registration fields

If your gateway needs to add fields to the registration for, you can use the fields() method. Restrict Content Pro will automatically append the return value for this method to the registration form when the gateway is selected.

For example, many payment gateways need to add a credit / debit card form to the registration form. If you need to do that, this example will be helpful:

public function fields() {
	ob_start();
	rcp_get_template_part( 'card-form' );
	return ob_get_clean();
}

You can read about  rcp_get_template_part() here. "card-form" is a template file provided by Restrict Content Pro that includes a standard credit / debit card template.

If you need any other fields, simply add your HTML to the fields() method.

Note: this method should be used for adding general fields to the registration form, this is for gateway-specific fields only.

Validating fields

If you need to validate fields that you have added to the registration form, you can use the validate_fields() method to do this.

When validating a field, you will use the add_error() method to register an error message that is then displayed on the registration form.

public function validate_fields() {
	if( empty( $_POST['rcp_card_number'] ) ) {
		$this->add_error( 'missing_card_number', __( 'The card number you have entered is invalid', 'rcp' ) );
	}
	if( empty( $_POST['rcp_card_cvc'] ) ) {
		$this->add_error( 'missing_card_code', __( 'The security code you have entered is invalid', 'rcp' ) );
	}
	if( empty( $_POST['rcp_card_zip'] ) ) {
		$this->add_error( 'missing_card_zip', __( 'The zip / postal code you have entered is invalid', 'rcp' ) );
	}
	if( empty( $_POST['rcp_card_name'] ) ) {
		$this->add_error( 'missing_card_name', __( 'The card holder name you have entered is invalid', 'rcp' ) );
	}
	if( empty( $_POST['rcp_card_exp_month'] ) ) {
		$this->add_error( 'missing_card_exp_month', __( 'The card expiration month you have entered is invalid', 'rcp' ) );
	}
	if( empty( $_POST['rcp_card_exp_year'] ) ) {
		$this->add_error( 'missing_card_exp_year', __( 'The card expiration year you have entered is invalid', 'rcp' ) );
	}
}

These error messages will be displayed on the registration form and will prevent the form from being submitted.

Processing webhooks

Webhooks are an integral part of payment processor integrations. They are what allow you to detect when a subscription payment has been made or a customer's subscription has been modified in some way from the merchant account.

There are two parts to processing webhooks:

  1. Telling your payment processor the URL to which webhooks should be sent
  2. Processing the webhooks in Restrict Content Pro after they are sent by the payment processor

Instructing the payment process where to send the webhook is different for every merchant processor, but often involves passing a URL to the parameters included in the API request when a subscription is created.

For example, PayPal uses a parameter called "notify_url", so we will use that.

We can include the webhook URL in the API call during process_signup():

public function process_signup() {
	// Set up the query args
	$args = array(
		'price'        => $this->initial_amount, // Includes initial price + signup fees + discounts. Just the base membership level price is $this->amount.
		'description'  => $this->subscription_name, // Name of the membership level.
		'custom'       => $this->membership->get_id(), // Store the membership ID as a unique identifier.
		'email'        => $this->email, // User's email address.
		'return'       => $this->return_url // Registration success URL.
		'notify_url'   => add_query_arg( 'listener', 'my_gateway_id', home_url( 'index.php' ) )
	);
	if( $this->auto_renew ) {
		$args['interval']       = $this->length_unit; // month, day, year
		$args['interval_count'] = $this->length; // 1, 2, 3, 4 . . . 
	}
	// Redirect to the external payment page
	wp_redirect( add_query_arg( $args, 'http://paymentpage.com/api/' ) );
	exit;
}

The URL is nothing more than the site's home URL with a special query arg attached to it. This query arg allows us to tell the difference between a webhook and a standard page request on the site.

Processing the webhooks is simple. All you need to do is place the logic for processing the request from your payment processor in the process_webhooks() method, like this:

public function process_webhooks() {
	// Determine if this is a webhook
	if( ! isset( $_GET['listener'] ) || strtoupper( $_GET['listener'] ) != 'my_gateway_id' ) {
		return;
	}
	// Now retrieve the webhook data from the $_POST super global
}

During the webhook, you will need to perform data validation and sanitization and then also make changes to the customer's account as appropriate. Here are common actions performs during a webhook:

  • Activating an account
  • Renewing a subscription
  • Recording a payment
  • Canceling a subscription

For these actions, you need to know how to modify memberships and how to create membership payments, both of which are described below in their appropriate sections.

To assist you in building your webhook processor, here is an example of how it works in PayPal Express:

Modifying memberships

Since building a payment gateway means manipulating user accounts (for cancellations, activations, payments, etc), it is important that you know how to adjust a member's subscription.

Restrict Content Pro includes a class called  RCP_Membership that will help you significantly in this area.

When using the class, you first need to instantiate it, like this:

$membership = rcp_get_membership( $membership_id );

If you have a gateway subscription ID then you can also retrieve a membership by that instead of by the membership ID. Here's an example:

$membership = rcp_get_membership_by( 'gateway_subscription_id', 'your_gateway_subscription_id' );

Once instantiated, you can call any of the public methods available.

For example, when you need to renew a membership (such as when a payment is process), you will do this:

$membership->renew( true );

The true parameter refers to whether the account is recurring. If the member has a recurring subscription, pass true, otherwise pass false.

The  renew() method will automatically calculate and set the expiration date based on the membership level.

When you need to cancel a membership, you can use the cancel() method:

$membership->cancel();

Activating a membership for the first time is usually done by completing the pending payment. The process of changing a pending payment's status to "complete" automatically activates the associated membership. This handles all the heavy lifting for you. It will be covered in the next section.

For additional examples, see the RCP_Membership class documentation.

Recording payments

Payments need to be recorded from the payment gateways in order to be displayed in the Restrict > Payments menu. Payments are usually recording during signup (for initial payments) and during webhook processing (for recurring subscription payments).

The  RCP_Payments class will be helpful for creating payment records in Restrict Content Pro.

Creating a payment looks like this:

$membership   = rcp_get_membership( $membership_id );
$payments = new RCP_Payments();
$args = array(
	'date'             => date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ), // Date the payment was made
	'subscription'     => $membership->get_membership_level_name(), // Name of the membership level
	'object_id'        => $membership->get_object_id(), // Membership level ID
	'payment_type'     => 'Credit Card', // Payment type. This is mostly for backwards compatibility.
	'gateway'          => 'my_gateway_id', // Gateway slug/ID
	'subscription_key' => $membership->get_subscription_key(), // Subscription key
	'amount'           => $amount, // Total amount
	'subtotal'         => $subtotal, // Subtotal
	'credits'          => $credits, // Credits applied
	'fees'             => $fees, // Signup fees
	'discount_amount'  => $discount_amount, // Amount discounted
	'discount_code'    => $discount_code, // Discount code used
	'user_id'          => $membership->get_customer()->get_user_id(), // ID of the user who made the payment
	'customer_id'      => $membership->get_customer()->get_id(), // ID of the customer
	'membership_id'    => $membership->get_id(), // ID of the membership
	'transaction_type' => 'new', // Type of transaction: either new, upgrade, downgrade, or renewal
	'transaction_id'   => $transaction_id, // Gateway transaction ID
	'status'           => 'complete', // Payment status - complete, pending, failed
);
$payments->insert( $args );

It is up to you to specify each of the necessary parameters. These are usually obtained from the POST data in the webhook or from the API response in the signup processing.

Completing a pending payment can be done once you have the relevant RCP_Membership object. The pending payment ID is stored with that record, so you need the membership object first. Once you have it, it can be completed like so:

$payments = new RCP_Payments();
$pending_payment_id = rcp_get_membership_meta( $membership->get_id(), 'pending_payment_id', true );

if ( ! empty( $pending_payment_id ) ) {
	$payment_data = array( 'status' => 'complete' ); // Array of data to update
	$payments->update( absint( $pending_payment_id ), $payment_data );
}

When a pending payment is completed, the corresponding membership is automatically activated. The customer is granted access and will be sent the Active Membership Email (if configured).

Complete gateway examples

To better assist you in creating your own payment gateway, we have two complete examples:

  1. The official Authorize.net payment gateway. This gateway is in a separate add-on so it's a good example of how to create a full standalone gateway in a separate plugin.
  2. A more abstracted example of how to use the API with fully commented code. These two files are also complete plugins, but they are not for a real payment gateway and are just examples of how to use the API.