Skip to content

Instantly share code, notes, and snippets.

@wturnerharris
Created November 13, 2013 04:02
Show Gist options
  • Save wturnerharris/7443520 to your computer and use it in GitHub Desktop.
Save wturnerharris/7443520 to your computer and use it in GitHub Desktop.
BrainTree extensions for Events Manager Pro Requires Braintree PHP libraries available via composer or direct download: https://www.braintreepayments.com/assets/client_libraries/php/braintree-php-2.23.1.tar.gz
<?php
if ( class_exists('EM_Gateway') ) :
class BraintreeGateway extends EM_Gateway {
var $gateway = 'braintree';
var $title = 'BrainTree';
var $status = 4;
var $status_txt = 'Processing (BrainTree)';
var $button_enabled = false;
var $supports_multiple_bookings = true;
var $registered_timer = 0;
/**
* Sets up gateaway and adds relevant actions/filters
*/
function __construct() {
parent::__construct();
if($this->is_active()) {
//Force SSL for booking submissions, since we have card info
if(get_option('em_'.$this->gateway.'_mode') == 'live'){ //no need if in sandbox mode
add_filter('em_wp_localize_script',array(&$this,'em_wp_localize_script'),10,1); //modify booking script, force SSL for all
add_filter('em_booking_form_action_url',array(&$this,'force_ssl'),10,1); //modify booking script, force SSL for all
}
add_action('em_booking_form_custom', array(&$this, 'booking_form_custom'),10,2);
add_action('em_booking_form_footer', array(&$this, 'booking_form_footer'),1,2);
remove_action('em_booking_form_footer', array('EM_Coupons', 'em_booking_form_footer'),1);
}
// required before any process can be made
add_action( 'braintree_init', array(&$this, 'configure'));
}
function configure(){
require_once __DIR__ . '/braintree/lib/Braintree.php';
Braintree_Configuration::environment( get_option( "em_{$this->gateway}_mode") );
Braintree_Configuration::merchantId( get_option( "em_{$this->gateway}_merchant_id") );
Braintree_Configuration::publicKey( get_option( "em_{$this->gateway}_public_key") );
Braintree_Configuration::privateKey( get_option( "em_{$this->gateway}_private_key") );
}
/*
* --------------------------------------------------
* Booking Interception - functions that modify booking object behaviour
* --------------------------------------------------
*/
/**
* This function intercepts the previous booking form url from the javascript localized array of EM variables and forces it to be an HTTPS url.
* @param array $localized_array
* @return array
*/
function em_wp_localize_script($localized_array){
$localized_array['bookingajaxurl'] = $this->force_ssl($localized_array['bookingajaxurl']);
return $localized_array;
}
/**
* Turns any url into an HTTPS url.
* @param string $url
* @return string
*/
function force_ssl($url){
return str_replace('http://','https://', $url);
}
/**
* Triggered by the em_booking_add_yourgateway action, modifies the booking status if the event isn't free and also adds a filter to modify user feedback returned.
* @param EM_Event $EM_Event
* @param EM_Booking $EM_Booking
* @param boolean $post_validation
*/
function booking_add($EM_Event,$EM_Booking, $post_validation = false){
global $wpdb, $wp_rewrite, $EM_Notices;
$this->registered_timer = current_time('timestamp', 1);
parent::booking_add($EM_Event, $EM_Booking, $post_validation);
if( $post_validation && empty($EM_Booking->booking_id) ){
if( get_option('dbem_multiple_bookings') && get_class($EM_Booking) == 'EM_Multiple_Booking' ){
add_filter('em_multiple_booking_save', array(&$this, 'em_booking_save'),2,2);
}else{
add_filter('em_booking_save', array(&$this, 'em_booking_save'),2,2);
}
}
}
/**
* Added to filters once a booking is added. Once booking is saved, we capture payment, and approve the booking (saving a second time). If payment isn't approved, just delete the booking and return false for save.
* @param bool $result
* @param EM_Booking $EM_Booking
*/
function em_booking_save( $result, $EM_Booking ){
global $wpdb, $wp_rewrite, $EM_Notices;
//make sure booking save was successful before we try anything
if( $result ){
if( $EM_Booking->get_price() > 0 ){
//handle results
$capture = $this->authorize_and_capture($EM_Booking);
if($capture){
//Set booking status, but no emails sent
if( !get_option('em_'.$this->gateway.'_manual_approval', false) || !get_option('dbem_bookings_approval') ){
$EM_Booking->set_status(1, false); //Approve
}else{
$EM_Booking->set_status(0, false); //Set back to normal "pending"
}
}else{
//not good.... error inserted into booking in capture function. Delete this booking from db
if( !is_user_logged_in() && get_option('dbem_bookings_anonymous') && !get_option('dbem_bookings_registration_disable') && !empty($EM_Booking->person_id) ){
//delete the user we just created, only if created after em_booking_add filter is called (which is when a new user for this booking would be created)
$EM_Person = $EM_Booking->get_person();
if( strtotime($EM_Person->data->user_registered) >= $this->registered_timer ){
if( is_multisite() ){
include_once(ABSPATH.'/wp-admin/includes/ms.php');
wpmu_delete_user($EM_Person->ID);
}else{
include_once(ABSPATH.'/wp-admin/includes/user.php');
wp_delete_user($EM_Person->ID);
}
//remove email confirmation
global $EM_Notices;
$EM_Notices->notices['confirms'] = array();
}
}
$EM_Booking->delete();
return false;
}
}
}
return $result;
}
/**
* Intercepts return data after a booking has been made and adds braintree vars, modifies feedback message.
* @param array $return
* @param EM_Booking $EM_Booking
* @return array
*/
function booking_form_feedback( $return, $EM_Booking = false ){
//Double check $EM_Booking is an EM_Booking object and that we have a booking awaiting payment.
if( !empty($return['result']) ){
if( !empty($EM_Booking->booking_meta['gateway']) && $EM_Booking->booking_meta['gateway'] == $this->gateway && $EM_Booking->get_price() > 0 ){
$return['message'] = sprintf("<p>%s</p>", get_option("em_{$this->gateway}_booking_feedback"));
}else{
//returning a free message
$return['message'] = sprintf("<p>%s</p>", get_option("em_{$this->gateway}_booking_feedback_free"));
}
}elseif( !empty($EM_Booking->booking_meta['gateway']) && $EM_Booking->booking_meta['gateway'] == $this->gateway && $EM_Booking->get_price() > 0 ){
//void this last authroization
$this->void($EM_Booking);
}
return $return;
}
/*
* --------------------------------------------------
* Booking UI - modifications to booking pages and tables containing braintree bookings
* --------------------------------------------------
*/
/**
* Outputs custom content and credit card information.
*/
function booking_form_custom(){
global $current_user;
if ( is_user_logged_in() ) {
$customer = self::get_payment_info($current_user->user_login);
if ($customer) $card = $customer->creditCards[0];
}
$dis = isset($card) ? ' disabled="disabled"' : '';
printf('<p>%s</p>', get_option('em_'.$this->gateway.'_form')); ?>
<?php if (!isset($card)) :?>
<p class="em-bookings-form-gateway-cardholder input-user-field">
<label for="billing_cardholder"><?php _e('Cardholder Name','em-pro'); ?> <span class="required-field">*</span> </label>
<input id="billing_cardholder" type="text" name="billing_cardholder" value="" class="input required"/>
</p>
<p class="em-bookings-form-gateway-cardno input-user-field">
<label for="cc_num"><?php _e('Credit Card Number','em-pro'); ?><span class="required-field">*</span></label>
<input id="cc_num" type="text" size="15" name="x_card_num" value="<?php if (isset($card)) echo $card->maskedNumber; ?>" class="input required number"<?php echo $dis; ?>/>
</p>
<p class="em-bookings-form-gateway-expiry input-user-field">
<label for="exp"><?php _e('Expiry Date','em-pro'); ?><span class="required-field">*</span></label>
<select id="exp" name="x_exp_date_month" class="required"<?php echo $dis; ?>>
<?php
for($i = 1; $i <= 12; $i++){
$m = $i > 9 ? $i:"0$i";
$sel = $m == $card->expirationMonth ? ' selected="selected"' : '';
echo "<option$sel>$m</option>";
}
?>
</select> /
<select name="x_exp_date_year" class="required"<?php echo $dis; ?>>
<?php
$year = date('Y',current_time('timestamp'));
for($i = $year; $i <= $year+10; $i++){
$sel = $i == $card->expirationYear ? ' selected="selected"' : '';
echo "<option$sel>$i</option>";
}
?>
</select>
</p>
<p class="em-bookings-form-ccv input-user-field">
<label for="ccv_num"><?php _e('CCV','em-pro'); ?><span class="required-field">*</span></label>
<input id="ccv_num" type="text" size="4" name="x_card_code" value="" class="input required number" />
</p>
<p><strong>Billing Address</strong></p>
<?php else: ?><p><b>Your credit card on file will be charged.</b><br/>Click <a href="<?php
echo site_url('my-account/#tab-membership');
?>" target="account">here</a> to change your billing information</p>
<?php endif; ?>
<?php if ( !isset($card)) : // collect billing info ?>
<p class="em-bookings-form-gateway-address1 input-user-field">
<label for="billing_adrress_1"><?php _e('Address Line 1','em-pro'); ?> <span class="required-field">*</span> </label>
<input id="billing_adrress_1" type="text" name="billing_adrress_1" value="" class="input required"/>
</p>
<p class="em-bookings-form-gateway-address2 input-user-field">
<label for="billing_address_2"><?php _e('Address Line 2','em-pro'); ?> </label>
<input id="billing_address_2" type="text" name="billing_address_2" value="" class="input"/>
</p>
<p class="em-bookings-form-gateway-city input-user-field">
<label for="billing_city"><?php _e('City','em-pro'); ?> <span class="required-field">*</span> </label>
<input id="billing_city" type="text" name="billing_city" value="" class="input required"/>
</p>
<p class="em-bookings-form-gateway-state input-user-field">
<label for="billing_state"><?php _e('State/Province','em-pro'); ?> </label>
<input id="billing_state" type="text" name="billing_state" value="" class="input"/>
</p>
<p class="em-bookings-form-gateway-zip input-user-field">
<label for="billing_zip"><?php _e('Zip/Postal Code','em-pro'); ?> </label>
<input id="billing_zip" type="text" name="billing_zip" value="" class="input"/>
</p>
<?php endif;
}
/**
* Overrides the coupon template in EM Pro.
* @param object $EM_Event
* @return string
*/
function booking_form_footer($EM_Event){
if( EM_Coupons::event_has_coupons($EM_Event) > 0){
?>
<br/><br/>
<p class="em-bookings-form-coupon">
<label><?php _e('Registration Code','em-pro'); ?></label>
<input type="text" name="coupon_code" class="input em-coupon-code" />
</p>
<?php
add_action('em_booking_js_footer', array('EM_Coupons', 'em_booking_js_footer') );
}
}
/*
* --------------------------------------------------
* BrainTree Functions - functions specific to braintree payments
* --------------------------------------------------
*/
static function get_payment_info($user_login = null){
if ( is_null($user_login) ) return false;
if ( !did_action('braintree_init') ) do_action('braintree_init');
try {
$customer = Braintree_Customer::find(hash('adler32', $user_login));
return $customer;
} catch ( Exception $e ) {
return false;
}
}
/**
* Retreive the braintree vars needed to send to the gateway to proceed with payment
* @param EM_Booking $EM_Booking
*/
function authorize_and_capture($EM_Booking){
global $EM_Notices, $current_user;
$user_login = is_user_logged_in() ? $current_user->user_login : false;
$args = array(
'amount' => $EM_Booking->get_price(false, false, true),
);
$ccv_code = @$_REQUEST['x_card_code'];
if ($user_login && $customer = self::get_payment_info($user_login)) {
// grab user info and payment token and process the request
$args['customerId'] = $customer->id;
$args['paymentMethodToken'] = $customer->creditCards[0]->token;
} else {
// no customer in vault, create a fresh transaction built from $_POST
$first_name = @$_REQUEST['first_name'];
$last_name = @$_REQUEST['last_name'];
$email = @$_REQUEST['user_email'];
$phone = @$_REQUEST['registration_phone'];
// should be setup as a default or selected form
if ( $first_name && $last_name && $email && $phone ) {
$args['customer']['firstName'] = $first_name;
$args['customer']['lastName'] = $last_name;
$args['customer']['email'] = $email;
$args['customer']['phone'] = $phone;
}
// should already be validated
$args['creditCard']['number'] = $_REQUEST['x_card_num'];
$args['creditCard']['expirationDate'] = $_REQUEST['x_exp_date_month'].'/'.$_REQUEST['x_exp_date_year'];
$args['creditCard']['cvv'] = $_REQUEST['x_card_code'];
$args['creditCard']['cardholderName'] = $_REQUEST['billing_cardholder'];
if (!empty($first_name)) $args['billing']['firstName'] = $first_name;
if (!empty($last_name)) $args['billing']['lastName'] = $last_name;
$guests = @$_REQUEST['registration_guests'];
$company = @$_REQUEST['registration_company'];
$address1 = @$_REQUEST['billing_address_1'];
$address2 = @$_REQUEST['billing_address_2'];
$city = @$_REQUEST['billing_city'];
$state = @$_REQUEST['billing_state'];
$zip = @$_REQUEST['billing_zip'];
if (!empty($company)) $args['billing']['company'] = $company;
if (!empty($address1)) $args['billing']['streetAddress'] = $company;
if (!empty($address2)) $args['billing']['extendedAddress'] = $company;
if (!empty($city)) $args['billing']['locality'] = $company;
if (!empty($state)) $args['billing']['region'] = $company;
if (!empty($zip)) $args['billing']['postalCode'] = $company;
}
//Get Payment
if ( !did_action('braintree_init') ) do_action('braintree_init');
$response = Braintree_Transaction::sale($args);
//Handle result
$result = $response->success == true;
if( $result ){
$headers[] = 'From: Site Admin <'.get_bloginfo('admin_email').'>' . "\r\n";
wp_mail( get_bloginfo("admin_email"), __("Booking Receipt", 'abana'), print_r($response, true), $headers );
$EM_Booking->booking_meta[$this->gateway] = array('txn_id'=>$response->transaction->id, 'amount' => $response->transaction->amount);
$this->record_transaction($EM_Booking, $response->transaction->amount, 'USD', date('Y-m-d H:i:s', current_time('timestamp')), $response->transaction->id, 'Completed', '');
}else{
foreach($response->errors->deepAll() AS $error) $EM_Booking->add_error("Error #{$error->code}: {$error->message}\n");
}
//Return transaction_id or false /* convert boolean to result of transaction */
return apply_filters('BraintreeGateway_authorize', $result, $EM_Booking, $this);
}
function void($EM_Booking){
if( !empty($EM_Booking->booking_meta[$this->gateway]) ){
$capture = $this->get_api();
$capture->amount = $EM_Booking->booking_meta[$this->gateway]['amount'];
$capture->void();
}
}
/*
* --------------------------------------------------
* Gateway Settings Functions
* --------------------------------------------------
*/
/**
* Outputs custom setting fields in the settings page
*/
function mysettings() {
global $EM_options;
?>
<table class="form-table">
<tbody>
<tr valign="top">
<th scope="row"><?php _e('Success Message', 'em-pro') ?></th>
<td>
<input type="text" name="booking_feedback" value="<?php esc_attr_e(get_option("em_{$this->gateway}_booking_feedback", "Booking Successful!" )); ?>" style='width: 40em;' /><br />
<em><?php _e('The message that is shown to a user when a booking is successful and payment has been taken.','em-pro'); ?></em>
</td>
</tr>
<tr valign="top">
<th scope="row"><?php _e('Success Free Message', 'em-pro') ?></th>
<td>
<input type="text" name="booking_feedback_free" value="<?php esc_attr_e(get_option("em_{$this->gateway}_booking_feedback_free", "Booking Successful! You have not been charged for this ticket." )); ?>" style='width: 40em;' /><br />
<em><?php _e('If some cases if you allow a free ticket (e.g. pay at gate) as well as paid tickets, this message will be shown and the user will not be charged.','em-pro'); ?></em>
</td>
</tr>
</tbody>
</table>
<h3><?php echo sprintf(__('%s Options','dbem'),'BrainTree')?></h3>
<table class="form-table">
<tbody>
<tr valign="top">
<th scope="row"><?php _e('Mode', 'em-pro'); ?></th>
<td>
<select name="mode">
<?php $selected = get_option('em_'.$this->gateway.'_mode'); ?>
<option value="sandbox" <?php echo ($selected == 'sandbox') ? 'selected="selected"':''; ?>><?php _e('Sandbox','emp-pro'); ?></option>
<option value="live" <?php echo ($selected == 'live') ? 'selected="selected"':''; ?>><?php _e('Live','emp-pro'); ?></option>
</select>
<br />
<em><?php echo sprintf(__('Please visit <a href="%s">BrainTree</a> to set up a sandbox account if you would like to test this gateway out. Alternatively, you can use your real account in test mode, which you can set on your BrainTree control panel.','em-pro'),'https://developer.BrainTree/integration/fifteenminutes/');?></em>
</td>
</tr>
<tr valign="top">
<th scope="row"><?php _e('Merchant ID', 'emp-pro') ?></th>
<td><input type="text" name="merchant_id" value="<?php esc_attr_e(get_option( "em_{$this->gateway}_merchant_id", "" )); ?>" /></td>
</tr>
<tr valign="top">
<th scope="row"><?php _e('Public Key', 'emp-pro') ?></th>
<td><input type="text" name="public_key" value="<?php esc_attr_e(get_option( "em_{$this->gateway}_public_key", "" )); ?>" /></td>
</tr>
<tr valign="top">
<th scope="row"><?php _e('Private Key', 'emp-pro') ?></th>
<td><input type="text" name="private_key" value="<?php esc_attr_e(get_option( "em_{$this->gateway}_private_key", "" )); ?>" /></td>
</tr>
<tr><td colspan="2">
<p><strong><?php echo __( 'Payment Notifications','em-pro'); ?></strong></p>
<p><?php _e('If you would like to receive notifications from BrainTree and handle refunds or voided transactions automatically, you need to enable Silent Post URL and provide an MD5 Hash in your merchant interface.','em-pro'); ?></p>
<p><?php echo sprintf(__('Your return url is %s.','em-pro'),'<code>'.$this->get_payment_return_url().'</code>'); ?> <?php _e('You can use this as your Silent Post URL.', 'em-pro'); ?></p>
</td></tr>
<tr><td colspan="2"><strong><?php echo sprintf(__( '%s Options', 'dbem' ),__('Advanced','em-pro')); ?></strong></td></tr>
<tr>
<th scope="row"><?php _e('Email Customer (on success)', 'emp-pro') ?></th>
<td>
<select name="email_customer">
<?php $selected = get_option('em_'.$this->gateway.'_email_customer'); ?>
<option value="1" <?php echo ($selected) ? 'selected="selected"':''; ?>><?php _e('Yes','emp-pro'); ?></option>
<option value="0" <?php echo (!$selected) ? 'selected="selected"':''; ?>><?php _e('No','emp-pro'); ?></option>
</select>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Customer Receipt Email Header', 'emp-pro') ?></th>
<td><input type="text" name="header_email_receipt" value="<?php esc_attr_e(get_option( 'em_'. $this->gateway . "_header_email_receipt", __("Thanks for your payment!", "emp-pro"))); ?>" /></td>
</tr>
<tr>
<th scope="row"><?php _e('Customer Receipt Email Footer', 'emp-pro') ?></th>
<td><input type="text" name="footer_email_receipt" value="<?php esc_attr_e(get_option( 'em_'. $this->gateway . "_footer_email_receipt", "" )); ?>" /></td>
</tr>
<tr valign="top">
<th scope="row"><?php _e('Manually approve completed transactions?', 'em-pro') ?></th>
<td>
<input type="checkbox" name="manual_approval" value="1" <?php echo (get_option('em_'. $this->gateway . "_manual_approval" )) ? 'checked="checked"':''; ?> /><br />
<em><?php _e('By default, when someone pays for a booking, it gets automatically approved once the payment is confirmed. If you would like to manually verify and approve bookings, tick this box.','em-pro'); ?></em><br />
<em><?php echo sprintf(__('Approvals must also be required for all bookings in your <a href="%s">settings</a> for this to work properly.','em-pro'),EM_ADMIN_URL.'&amp;page=events-manager-options'); ?></em>
</td>
</tr>
</tbody>
</table>
<?php
}
/*
* Run when saving settings, saves the settings available in BraintreeGateway::mysettings()
*/
function update() {
parent::update();
$gateway_options = array(
$this->gateway . "_mode" => $_REQUEST[ 'mode' ],
$this->gateway . "_merchant_id" => $_REQUEST[ 'merchant_id' ],
$this->gateway . "_public_key" => $_REQUEST[ 'public_key' ],
$this->gateway . "_private_key" => $_REQUEST[ 'private_key' ],
$this->gateway . "_email_customer" => ($_REQUEST[ 'email_customer' ]),
$this->gateway . "_header_email_receipt" => $_REQUEST[ 'header_email_receipt' ],
$this->gateway . "_footer_email_receipt" => $_REQUEST[ 'footer_email_receipt' ],
$this->gateway . "_manual_approval" => $_REQUEST[ 'manual_approval' ],
$this->gateway . "_booking_feedback" => wp_kses_data($_REQUEST[ 'booking_feedback' ]),
$this->gateway . "_booking_feedback_free" => wp_kses_data($_REQUEST[ 'booking_feedback_free' ])
);
foreach($gateway_options as $key=>$option){
update_option('em_'.$key, stripslashes($option));
}
//default action is to return true
return true;
}
}
EM_Gateways::register_gateway('braintree', 'BraintreeGateway');
endif;
@johnmoriarty
Copy link

Hey - thanks for the script. Super useful. I know this is 2 years old for you, but I wondered if you might be able to help me identify an issue... Of course, no worries if not.

When I have Braintree set as the gateway using your code and I view a single event page, I'm getting a PHP issue where the rest of the page doesn't load after the Booking UI starting at the credit card form fields.

I've narrowed it down to a single line - 164: the declaration of the $customer variable here:

global $current_user;
if ( is_user_logged_in() ) {
    $customer = self::get_payment_info($current_user->user_login);   // <-- problem child
    if ($customer) $card = $customer->creditCards[0];
}

If I comment that one line out, everything loads fine, though of course that saved card functionality won't work I'm sure. Do you have any quick sense of what might be causing the issue? Surely its my own set-up, but I'm not really savvy enough to know where I went wrong.

Any insight would be greatly appreciated. No sweat if not. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment