Skip to content

CakePHP Braintree Plugin

anthonyp edited this page Sep 14, 2012 · 8 revisions

CakePHP Braintree Plugin Documentation

The Concept

The Vault

This plugin is based completely around capturing credit card information to the Braintree vault using Braintree's Transpaent Redirect method. It helps you easily output a form that can capture billing information to a vault record, and then use that record to make charges against at any point in the future. This is useful for traditional ecommerce, or for subscription-based payments. But because it does not interact directly with Braintree's subscription features (instead opting to just store everything in the vault), you must develop your own scheduling system should you want to manage recurring payments. This is great if you need flexibility, as it allows you to develop the system exactly how you want it, and then use the plugin to easily trigger a transaction of any amount, at any point in time.

Local Synchronization

A group of local MySQL tables are created and used to synchronize your CakePHP application with the Braintree vault. This allows for maximum performance and flexibility; you will have a local record of everything stored in the vault that you can query against, which leaves the use cases up to your imagination -- link credit card IDs up to recurring transaction rows in the same database, find & email all customers with credit cards that are about to expire, etc.

Environment & Credentials

Sandbox vs. Production

When in shell mode, the sandbox or production environment is used based on the debug setting in CakePHP. Make sure it is zero if you want to use production, or anything greater than zero if you would like to test in the sandbox.

Setting Your API Credentials

You can use one or more Braintree accounts. All API credentials are stored in the braintree_merchants table, so populate that table with your information. In order to let Braintree know which account you want to use at any given time, you must set the credentials before any calls to the API. Preferably, this logic lives in the beforeFilter() of any controller/action which accesses Braintree:

$merchant_id = 1; // the merchant ID you want to use  
$braintree_configs = ClassRegistry::init('Braintree.BraintreeMerchant')->find('first', array(  
     'conditions' => array(  
          'BraintreeMerchant.id' => $merchant_id  
     ),  
     'contain' => false  
));  
BraintreeConfig::set(array(  
	'merchantId' => $merchant_id,  
	'publicKey' => $braintree_configs['BraintreeMerchant']['braintree_public_key'],  
	'privateKey' => $braintree_configs['BraintreeMerchant']['braintree_private_key'],
	'environment' => 'production' // 'sandbox' or 'production'
));  

Configuring a Vault Intake Flow

Using the Braintree Customer Model to Create a New Customer Record

In order to output the credit card intake form, you must first have a customer record to associate the credit card data with. The best way to do this is to use a convenience method in the BraintreeCustomer model from within the controller action that will output the intake form.

Example:

public function credit_card_intake () {
     // $app_customer contains an array of information about the customer record in our application
     $braintree_customer_id = ClassRegistry::init('Braintree.BraintreeCustomer')->getOrCreateCustomerId(
         'Customer', // A model in your app that you want to associate the Braintree customer with
         $app_customer['id'], // A foreign_id in your app that you want to associate the Braintree customer with
         array(
    	     'first_name' => $app_customer_first_name,
    	     'last_name' => $app_customer['last_name'],
    	     'company' => $app_customer['company'],
    	     'email' => $app_customer['email'],
    	     'phone' => $app_customer['phone'],
    	     'fax' => $app_customer['fax'],
    	     'website' => $app_customer['website']
         )
     );
     $this->set('braintree_customer_id', $braintree_customer_id);
}

Using the 'vault_form' Element to Output a Credit Card Intake Form

There is an element that makes it extremely simple to output a nicely styled credit card intake form that submits directly to Braintree and redirects back to your website with status & token information. All of the options for the form are documented at the top of the element, so be sure to look at the "elements/credit_cards/vault_form.ctp" file.

Example:

echo $this->element(
	'credit_cards/vault_form',
	array(
		'plugin' => 'braintree',
		'braintree_merchant_id' => 1, // the merchant ID you want to use
		'braintree_customer_id' => $braintree_customer_id,
		'billing_address' => array(
			'first_name' => $this->Session->read('previously_input_first_name'),
			'last_name' => $this->Session->read('previously_input_last_name'),
			'company' => $this->Session->read('previously_input_company'),
			'address1' => $this->Session->read('previously_input_address1'),
			'address2' => $this->Session->read('previously_input_address2'),
			'city' => $this->Session->read('previously_input_city'),
			'state' => $this->Session->read('previously_input_state'),
			'postal_code' => $this->Session->read('previously_input_postal_code'),
			'country_code_alpha_2' => $this->Session->read('previously_input_country_code_alpha_2')
		),
		'verify_credit_card' => true, // do you want to verify credit cards before accepting them? (costs more, but makes life easier)
		'foreign_model' => 'Order', // A model in your app that you want to associate the Braintree vault record with
		'foreign_id' => $this->Session->read('order_id_in_progress') // A foreign_id in your app that you want to associate the Braintree vault record with
	)
); 

Setting up the App to Process the Status & Token Data Braintree Sends Back

The credit card intake form is set up to redirect back to the same controller & action it came from. But the controller needs to understand how to process it. So, in the controller containing the credit_card_intake() action, the BraintreeCallback component needs to be included.

Example:

public $components = array(
	'Braintree.BraintreeCallback' => array(
		'callback_actions' => array(
			'credit_card_intake' => array( // The name of the action you'd like the BraintreeCallback component to watch for incoming Braintree data
				'redirect' => array( // The URL you'd like to redirect to after successfully storing the vault token
					'action' => 'review_order'
				)
			)
		)
	)
);

If there are errors storing the credit card information in the vault, they are stored in the BraintreeCallback's "braintree_error" property and can be used however you'd like. For example, in the credit_card_intake() action, the following logic could be inserted:

if (!empty($this->BraintreeCallback->braintree_error)) {
     $this->Session->setFlash(
          nl2br($this->BraintreeCallback->braintree_error),
          'error'
     );
     $this->redirect(array('action' => $this->params['action']));
}

Advanced BraintreeCallback Usage

You may want to do some custom process with the Braintree status/token result. You can easily extend the component into your own component and take advantage of the various callbacks available. For example

App::import('Component', 'Braintree.BraintreeCallback');
class CustomBraintreeCallbackComponent extends BraintreeCallbackComponent {
     public function onSuccess ($result) {
          if (!parent::onSuccess($result)) {
               return false;
          }
          // do some custom "success" logic with the $result here
          return true;
     }
}

Creating a Transaction

Once you have a customer & that customer's billing information captured to a vault record, you will most likely want to charge transactions against it. You may want to do this once (in the case of a simple order), or many times over the course of months/years, using your own home-brewed subscription logic. Either are possible. To create a transaction, simply use the BraintreeTransaction model. For example:

$result = ClassRegistry::init('BraintreeTransaction')->save(array(
     'BraintreeTransaction' => array(
           'braintree_customer_id' => $the_customer_id_you_are_creating_a_transaction_against,
           'braintree_credit_card_id' => $the_credit_card_token_you_are_creating_a_transaction_against,
           'model' => 'Order', // A model in your app that you want to associate the Braintree vault record with
           'foreign_id' => 1, // A foreign_id in your app that you want to associate the Braintree transaction with
           'type' => 'sale',
           'amount' => '50.00'
     )
));

Authorization, captures, refunds, and voids can be done in a similar fashion. For example:

  • Authorization: Use type = "authorization" instead of type = "sale"
  • Refund: Use type = "credit" instead of type = "sale", with the braintree_transaction_id equal to the transaction ID being refunded

For any transaction acting as an update to a previous transaction, ensure the ID of the transaction is set before saving:

  • Capture: Set type = "sale" on a transaction previously saved as type = "authorization"
  • Void: Set status = "voided" on a transaction previously saved as type = "submitted_for_settlement"

Updating Transaction Statuses via the Shell

Transaction statuses change over the course of time and your application needs a way to know about this. For example, if you submit a transaction right now, it may not settle for another few hours. That is the purpose of the shell script, which will update your local database with the freshest statuses whenever it is run. The command is:

/path/to/app/../cake/console/cake.php braintree update_transaction_statuses

The script takes an optional argument for updating transactions in either the sandbox or production. For example:

/path/to/app/../cake/console/cake.php braintree update_transaction_statuses production

Advanced Usage, Final Notes

Remember that a plugin is nothing more than an object-oriented mini-application.

Every model in this plugin can be queried to allow for some very advanced usage. For example, query the BraintreeCreditCard model using custom conditions to find all credit cards that are expiring. Anything you set your mind to.

For maximum flexibility/understanding every piece of code in this plugin can be analyzed and then extended to suit; this wiki is merely a guide, not a rule book.

Please email me at contact@anthonyputignano.com with any questions or suggestions.