PMProGateway_braintree

The Paid Memberships Pro PMProGateway braintree class.

Defined (1)

The class is defined in the following location(s).

/classes/gateways/class.pmprogateway_braintree.php  
  1. class PMProGateway_braintree extends PMProGateway 
  2. /** 
  3. * @var bool Is the Braintree/PHP Library loaded 
  4. */ 
  5. private static $is_loaded = false; 
  6.  
  7. function __construct($gateway = NULL) 
  8. $this->gateway = $gateway; 
  9. $this->gateway_environment = pmpro_getOption("gateway_environment"); 
  10.  
  11. if( true === $this->dependencies() ) { 
  12. $this->loadBraintreeLibrary();  
  13.  
  14. //convert to braintree nomenclature 
  15. $environment = $this->gateway_environment; 
  16. if($environment == "live") 
  17. $environment = "production"; 
  18.  
  19. Braintree_Configuration::environment($environment); 
  20. Braintree_Configuration::merchantId(pmpro_getOption("braintree_merchantid")); 
  21. Braintree_Configuration::publicKey(pmpro_getOption("braintree_publickey")); 
  22. Braintree_Configuration::privateKey(pmpro_getOption("braintree_privatekey")); 
  23. self::$is_loaded = true; 
  24.  
  25. return $this->gateway; 
  26. }  
  27. /** 
  28. * Warn if required extensions aren't loaded. 
  29. * @return bool 
  30. * @since 1.8.6.8.1 
  31. */ 
  32. public static function dependencies() 
  33. global $msg, $msgt, $pmpro_braintree_error; 
  34.  
  35. if ( version_compare( PHP_VERSION, '5.4.45', '<' )) { 
  36.  
  37. $msg = -1; 
  38. $msgt = sprintf(__("The Braintree Gateway requires PHP 5.4.45 or greater. We recommend upgrading to PHP %s or greater. Ask your host to upgrade.", "paid-memberships-pro" ), PMPRO_PHP_MIN_VERSION );  
  39.  
  40. pmpro_setMessage( $msgt, "pmpro_error" ); 
  41. return false; 
  42.  
  43. $modules = array('xmlwriter', 'SimpleXML', 'openssl', 'dom', 'hash', 'curl'); 
  44.  
  45. foreach($modules as $module) { 
  46. if(!extension_loaded($module)) { 
  47. $pmpro_braintree_error = true;  
  48. $msg = -1; 
  49. $msgt = sprintf(__("The %s gateway depends on the %s PHP extension. Please enable it, or ask your hosting provider to enable it.", 'paid-memberships-pro' ), 'Braintree', $module); 
  50.  
  51. //throw error on checkout page 
  52. if(!is_admin()) 
  53. pmpro_setMessage($msgt, 'pmpro_error'); 
  54.  
  55. return false; 
  56.  
  57. self::$is_loaded = true; 
  58. return true; 
  59.  
  60. /** 
  61. * Load the Braintree API library. 
  62. *  
  63. * @since 1.8.1 
  64. * Moved into a method in version 1.8.1 so we only load it when needed. 
  65. */ 
  66. function loadBraintreeLibrary() 
  67. //load Braintree library if it hasn't been loaded already (usually by another plugin using Braintree) 
  68. if(!class_exists("\\Braintree")) 
  69. require_once( PMPRO_DIR . "/includes/lib/Braintree/lib/Braintree.php"); 
  70. }  
  71.  
  72. /** 
  73. * Get a collection of plans available for this Braintree account. 
  74. */ 
  75. function getPlans($force = false) { 
  76. //check for cache 
  77. $cache_key = 'pmpro_braintree_plans_' . md5($this->gateway_environment . pmpro_getOption("braintree_merchantid") . pmpro_getOption("braintree_publickey") . pmpro_getOption("braintree_privatekey")); 
  78.  
  79. $plans = get_transient($cache_key); 
  80.  
  81. //check Braintree if no transient found 
  82. if($plans === false) { 
  83. $plans = Braintree_Plan::all(); 
  84.  
  85. set_transient($cache_key, $plans); 
  86.  
  87. return $plans; 
  88.  
  89. /** 
  90. * Search for a plan by id 
  91. */ 
  92. function getPlanByID($id) { 
  93. $plans = $this->getPlans(); 
  94.  
  95. if(!empty($plans)) { 
  96. foreach($plans as $plan) { 
  97. if($plan->id == $id) 
  98. return $plan; 
  99.  
  100. return false; 
  101.  
  102. /** 
  103. * Checks if a level has an associated plan. 
  104. */ 
  105. static function checkLevelForPlan($level_id) { 
  106. $Gateway = new PMProGateway_braintree(); 
  107. $plan = $Gateway->getPlanByID('pmpro_' . $level_id); 
  108. if(!empty($plan)) 
  109. return true; 
  110. else 
  111. return false; 
  112.  
  113. /** 
  114. * Run on WP init 
  115. *  
  116. * @since 1.8 
  117. */ 
  118. static function init() 
  119. {  
  120. //make sure Braintree Payments is a gateway option 
  121. add_filter('pmpro_gateways', array('PMProGateway_braintree', 'pmpro_gateways')); 
  122.  
  123. //add fields to payment settings 
  124. add_filter('pmpro_payment_options', array('PMProGateway_braintree', 'pmpro_payment_options')); 
  125. add_filter('pmpro_payment_option_fields', array('PMProGateway_braintree', 'pmpro_payment_option_fields'), 10, 2); 
  126.  
  127. //code to add at checkout if Braintree is the current gateway 
  128. $default_gateway = pmpro_getOption('gateway'); 
  129. $current_gateway = pmpro_getGateway();  
  130. if( ( $default_gateway == "braintree" || $current_gateway == "braintree" && empty($_REQUEST['review']))) //$_REQUEST['review'] means the PayPal Express review page 
  131. add_action('pmpro_checkout_before_submit_button', array('PMProGateway_braintree', 'pmpro_checkout_before_submit_button')); 
  132. add_action('pmpro_billing_before_submit_button', array('PMProGateway_braintree', 'pmpro_checkout_before_submit_button')); 
  133. add_filter('pmpro_checkout_order', array('PMProGateway_braintree', 'pmpro_checkout_order')); 
  134. add_filter('pmpro_billing_order', array('PMProGateway_braintree', 'pmpro_checkout_order')); 
  135. add_filter('pmpro_required_billing_fields', array('PMProGateway_braintree', 'pmpro_required_billing_fields'));  
  136. add_filter('pmpro_include_payment_information_fields', array('PMProGateway_braintree', 'pmpro_include_payment_information_fields')); 
  137.  
  138. /** 
  139. * Make sure this gateway is in the gateways list 
  140. *  
  141. * @since 1.8 
  142. */ 
  143. static function pmpro_gateways($gateways) 
  144. if(empty($gateways['braintree'])) 
  145. $gateways['braintree'] = __('Braintree Payments', 'paid-memberships-pro' ); 
  146.  
  147. return $gateways; 
  148.  
  149. /** 
  150. * Get a list of payment options that the this gateway needs/supports. 
  151. *  
  152. * @since 1.8 
  153. */ 
  154. static function getGatewayOptions() 
  155. {  
  156. $options = array( 
  157. 'sslseal',  
  158. 'nuclear_HTTPS',  
  159. 'gateway_environment',  
  160. 'braintree_merchantid',  
  161. 'braintree_publickey',  
  162. 'braintree_privatekey',  
  163. 'braintree_encryptionkey',  
  164. 'currency',  
  165. 'use_ssl',  
  166. 'tax_state',  
  167. 'tax_rate',  
  168. 'accepted_credit_cards' 
  169. ); 
  170.  
  171. return $options; 
  172.  
  173. /** 
  174. * Set payment options for payment settings page. 
  175. *  
  176. * @since 1.8 
  177. */ 
  178. static function pmpro_payment_options($options) 
  179. {  
  180. //get Braintree options 
  181. $braintree_options = self::getGatewayOptions(); 
  182.  
  183. //merge with others. 
  184. $options = array_merge($braintree_options, $options); 
  185.  
  186. return $options; 
  187.  
  188. /** 
  189. * Display fields for this gateway's options. 
  190. *  
  191. * @since 1.8 
  192. */ 
  193. static function pmpro_payment_option_fields($values, $gateway) 
  194. ?> 
  195. <tr class="pmpro_settings_divider gateway gateway_braintree" <?php if($gateway != "braintree") { ?>style="display: none;"<?php } ?>> 
  196. <td colspan="2"> 
  197. <?php _e('Braintree Settings', 'paid-memberships-pro' ); ?> 
  198. </td> 
  199. </tr> 
  200. <tr class="gateway gateway_braintree" <?php if($gateway != "braintree") { ?>style="display: none;"<?php } ?>> 
  201. <th scope="row" valign="top"> 
  202. <label for="braintree_merchantid"><?php _e('Merchant ID', 'paid-memberships-pro' );?>:</label> 
  203. </th> 
  204. <td> 
  205. <input type="text" id="braintree_merchantid" name="braintree_merchantid" size="60" value="<?php echo esc_attr($values['braintree_merchantid'])?>" /> 
  206. </td> 
  207. </tr> 
  208. <tr class="gateway gateway_braintree" <?php if($gateway != "braintree") { ?>style="display: none;"<?php } ?>> 
  209. <th scope="row" valign="top"> 
  210. <label for="braintree_publickey"><?php _e('Public Key', 'paid-memberships-pro' );?>:</label> 
  211. </th> 
  212. <td> 
  213. <input type="text" id="braintree_publickey" name="braintree_publickey" size="60" value="<?php echo esc_attr($values['braintree_publickey'])?>" /> 
  214. </td> 
  215. </tr> 
  216. <tr class="gateway gateway_braintree" <?php if($gateway != "braintree") { ?>style="display: none;"<?php } ?>> 
  217. <th scope="row" valign="top"> 
  218. <label for="braintree_privatekey"><?php _e('Private Key', 'paid-memberships-pro' );?>:</label> 
  219. </th> 
  220. <td> 
  221. <input type="text" id="braintree_privatekey" name="braintree_privatekey" size="60" value="<?php echo esc_attr($values['braintree_privatekey'])?>" /> 
  222. </td> 
  223. </tr> 
  224. <tr class="gateway gateway_braintree" <?php if($gateway != "braintree") { ?>style="display: none;"<?php } ?>> 
  225. <th scope="row" valign="top"> 
  226. <label for="braintree_encryptionkey"><?php _e('Client-Side Encryption Key', 'paid-memberships-pro' );?>:</label> 
  227. </th> 
  228. <td> 
  229. <textarea id="braintree_encryptionkey" name="braintree_encryptionkey" rows="3" cols="80"><?php echo esc_textarea($values['braintree_encryptionkey'])?></textarea>  
  230. </td> 
  231. </tr> 
  232. <tr class="gateway gateway_braintree" <?php if($gateway != "braintree") { ?>style="display: none;"<?php } ?>> 
  233. <th scope="row" valign="top"> 
  234. <label><?php _e('Web Hook URL', 'paid-memberships-pro' );?>:</label> 
  235. </th> 
  236. <td> 
  237. <p> 
  238. <?php _e('To fully integrate with Braintree, be sure to set your Web Hook URL to', 'paid-memberships-pro' );?>  
  239. <pre><?php  
  240. //echo admin_url("admin-ajax.php") . "?action=braintree_webhook"; 
  241. echo PMPRO_URL . "/services/braintree-webhook.php"; 
  242. ?></pre> 
  243. </p> 
  244. </td> 
  245. </tr> 
  246. <?php 
  247.  
  248. /** 
  249. * Filtering orders at checkout. 
  250. *  
  251. * @since 1.8 
  252. */ 
  253. static function pmpro_checkout_order($morder) 
  254. {  
  255. //load up values 
  256. if(isset($_REQUEST['number']))  
  257. $braintree_number = $_REQUEST['number']; 
  258. else 
  259. $braintree_number = ""; 
  260.  
  261. if(isset($_REQUEST['expiration_date']))  
  262. $braintree_expiration_date = $_REQUEST['expiration_date']; 
  263. else 
  264. $braintree_expiration_date = ""; 
  265.  
  266. if(isset($_REQUEST['cvv'])) 
  267. $braintree_cvv = $_REQUEST['cvv']; 
  268. else 
  269. $braintree_cvv = "";  
  270.  
  271. $morder->braintree = new stdClass(); 
  272. $morder->braintree->number = $braintree_number; 
  273. $morder->braintree->expiration_date = $braintree_expiration_date; 
  274. $morder->braintree->cvv = $braintree_cvv;  
  275.  
  276. return $morder; 
  277.  
  278. /** 
  279. * Don't require the CVV, but look for cvv (lowercase) that braintree sends 
  280. *  
  281. */ 
  282. static function pmpro_required_billing_fields($fields) 
  283. unset($fields['CVV']);  
  284. $fields['cvv'] = true; 
  285. return $fields; 
  286.  
  287. /** 
  288. * Add some hidden fields and JavaScript to checkout. 
  289. *  
  290. */ 
  291. static function pmpro_checkout_before_submit_button() 
  292. ?> 
  293. <input type='hidden' data-encrypted-name='expiration_date' id='credit_card_exp' /> 
  294. <input type='hidden' name='AccountNumber' id='BraintreeAccountNumber' /> 
  295. <script type="text/javascript" src="https://js.braintreegateway.com/v1/braintree.js"></script> 
  296. <script type="text/javascript"> 
  297. <!-- 
  298. //set up braintree encryption 
  299. var braintree = Braintree.create('<?php echo pmpro_getOption("braintree_encryptionkey"); ?>'); 
  300. braintree.onSubmitEncryptForm('pmpro_form'); 
  301.  
  302. //pass expiration dates in original format 
  303. function pmpro_updateBraintreeCardExp() 
  304. jQuery('#credit_card_exp').val(jQuery('#ExpirationMonth').val() + "/" + jQuery('#ExpirationYear').val()); 
  305. jQuery('#ExpirationMonth, #ExpirationYear').change(function() { 
  306. pmpro_updateBraintreeCardExp(); 
  307. }); 
  308. pmpro_updateBraintreeCardExp(); 
  309.  
  310. //pass last 4 of credit card 
  311. function pmpro_updateBraintreeAccountNumber() 
  312. jQuery('#BraintreeAccountNumber').val('XXXXXXXXXXXXX' + jQuery('#AccountNumber').val().substr(jQuery('#AccountNumber').val().length - 4)); 
  313. jQuery('#AccountNumber').change(function() { 
  314. pmpro_updateBraintreeAccountNumber(); 
  315. }); 
  316. pmpro_updateBraintreeAccountNumber(); 
  317. --> 
  318. </script> 
  319. <?php 
  320.  
  321. /** 
  322. * Use our own payment fields at checkout. (Remove the name attributes and set some data-encrypted-name attributes.) 
  323. * @since 1.8 
  324. */ 
  325. static function pmpro_include_payment_information_fields($include) 
  326.  
  327. //global vars 
  328. global $pmpro_requirebilling, $pmpro_show_discount_code, $discount_code, $CardType, $AccountNumber, $ExpirationMonth, $ExpirationYear; 
  329.  
  330. //get accepted credit cards 
  331. $pmpro_accepted_credit_cards = pmpro_getOption("accepted_credit_cards"); 
  332. $pmpro_accepted_credit_cards = explode(", ", $pmpro_accepted_credit_cards); 
  333. $pmpro_accepted_credit_cards_string = pmpro_implodeToEnglish($pmpro_accepted_credit_cards);  
  334.  
  335. //include ours 
  336. ?> 
  337. <table id="pmpro_payment_information_fields" class="pmpro_checkout top1em" width="100%" cellpadding="0" cellspacing="0" border="0" <?php if(!$pmpro_requirebilling || apply_filters("pmpro_hide_payment_information_fields", false) ) { ?>style="display: none;"<?php } ?>> 
  338. <thead> 
  339. <tr> 
  340. <th> 
  341. <span class="pmpro_thead-name"><?php _e('Payment Information', 'paid-memberships-pro' );?></span> 
  342. <span class="pmpro_thead-msg"><?php printf(__('We Accept %s', 'paid-memberships-pro' ), $pmpro_accepted_credit_cards_string);?></span> 
  343. </th> 
  344. </tr> 
  345. </thead> 
  346. <tbody>  
  347. <tr valign="top">  
  348. <td>  
  349. <?php 
  350. $sslseal = pmpro_getOption("sslseal"); 
  351. if($sslseal) 
  352. ?> 
  353. <div class="pmpro_sslseal"><?php echo stripslashes($sslseal)?></div> 
  354. <?php 
  355. ?> 
  356. <?php  
  357. $pmpro_include_cardtype_field = apply_filters('pmpro_include_cardtype_field', true); 
  358. if($pmpro_include_cardtype_field)  
  359. ?> 
  360. <div class="pmpro_payment-card-type"> 
  361. <label for="CardType"><?php _e('Card Type', 'paid-memberships-pro' );?></label> 
  362. <select id="CardType" name="CardType" class=" <?php echo pmpro_getClassForField("CardType");?>"> 
  363. <?php foreach($pmpro_accepted_credit_cards as $cc) { ?> 
  364. <option value="<?php echo $cc?>" <?php if($CardType == $cc) { ?>selected="selected"<?php } ?>><?php echo $cc?></option> 
  365. <?php } ?>  
  366. </select>  
  367. </div> 
  368. <?php  
  369. }  
  370. ?> 
  371.  
  372. <div class="pmpro_payment-account-number"> 
  373. <label for="AccountNumber"><?php _e('Card Number', 'paid-memberships-pro' );?></label> 
  374. <input id="AccountNumber" name="AccountNumber" class="input <?php echo pmpro_getClassForField("AccountNumber");?>" type="text" size="25" value="<?php echo esc_attr($AccountNumber)?>" data-encrypted-name="number" autocomplete="off" />  
  375. </div> 
  376.  
  377. <div class="pmpro_payment-expiration"> 
  378. <label for="ExpirationMonth"><?php _e('Expiration Date', 'paid-memberships-pro' );?></label> 
  379. <select id="ExpirationMonth" name="ExpirationMonth" class=" <?php echo pmpro_getClassForField("ExpirationMonth");?>"> 
  380. <option value="01" <?php if($ExpirationMonth == "01") { ?>selected="selected"<?php } ?>>01</option> 
  381. <option value="02" <?php if($ExpirationMonth == "02") { ?>selected="selected"<?php } ?>>02</option> 
  382. <option value="03" <?php if($ExpirationMonth == "03") { ?>selected="selected"<?php } ?>>03</option> 
  383. <option value="04" <?php if($ExpirationMonth == "04") { ?>selected="selected"<?php } ?>>04</option> 
  384. <option value="05" <?php if($ExpirationMonth == "05") { ?>selected="selected"<?php } ?>>05</option> 
  385. <option value="06" <?php if($ExpirationMonth == "06") { ?>selected="selected"<?php } ?>>06</option> 
  386. <option value="07" <?php if($ExpirationMonth == "07") { ?>selected="selected"<?php } ?>>07</option> 
  387. <option value="08" <?php if($ExpirationMonth == "08") { ?>selected="selected"<?php } ?>>08</option> 
  388. <option value="09" <?php if($ExpirationMonth == "09") { ?>selected="selected"<?php } ?>>09</option> 
  389. <option value="10" <?php if($ExpirationMonth == "10") { ?>selected="selected"<?php } ?>>10</option> 
  390. <option value="11" <?php if($ExpirationMonth == "11") { ?>selected="selected"<?php } ?>>11</option> 
  391. <option value="12" <?php if($ExpirationMonth == "12") { ?>selected="selected"<?php } ?>>12</option> 
  392. </select>/<select id="ExpirationYear" name="ExpirationYear" class=" <?php echo pmpro_getClassForField("ExpirationYear");?>"> 
  393. <?php 
  394. for($i = date_i18n("Y"); $i < date_i18n("Y") + 10; $i++) 
  395. ?> 
  396. <option value="<?php echo $i?>" <?php if($ExpirationYear == $i) { ?>selected="selected"<?php } ?>><?php echo $i?></option> 
  397. <?php 
  398. ?> 
  399. </select>  
  400. </div> 
  401.  
  402. <?php 
  403. $pmpro_show_cvv = apply_filters("pmpro_show_cvv", true); 
  404. if($pmpro_show_cvv) 
  405. {  
  406. ?> 
  407. <div class="pmpro_payment-cvv"> 
  408. <label for="CVV"><?php _e('CVV', 'paid-memberships-pro' );?></label> 
  409. <input class="input" id="CVV" name="cvv" type="text" size="4" value="<?php if(!empty($_REQUEST['CVV'])) { echo esc_attr($_REQUEST['CVV']); }?>" class=" <?php echo pmpro_getClassForField("CVV");?>" data-encrypted-name="cvv" /> <small>(<a href="javascript:void(0);" onclick="javascript:window.open('<?php echo pmpro_https_filter(PMPRO_URL)?>/pages/popup-cvv.html', 'cvv', 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=yes, resizable=yes, width=600, height=475');"><?php _e("what's this?", 'paid-memberships-pro' );?></a>)</small> 
  410. </div> 
  411. <?php 
  412. ?> 
  413.  
  414. <?php if($pmpro_show_discount_code) { ?> 
  415. <div class="pmpro_payment-discount-code"> 
  416. <label for="discount_code"><?php _e('Discount Code', 'paid-memberships-pro' );?></label> 
  417. <input class="input <?php echo pmpro_getClassForField("discount_code");?>" id="discount_code" name="discount_code" type="text" size="20" value="<?php echo esc_attr($discount_code)?>" /> 
  418. <input type="button" id="discount_code_button" name="discount_code_button" value="<?php _e('Apply', 'paid-memberships-pro' );?>" /> 
  419. <p id="discount_code_message" class="pmpro_message" style="display: none;"></p> 
  420. </div> 
  421. <?php } ?> 
  422.  
  423. </td>  
  424. </tr> 
  425. </tbody> 
  426. </table> 
  427. <?php 
  428.  
  429. //don't include the default 
  430. return false; 
  431.  
  432. /** 
  433. * Process checkout. 
  434. *  
  435. */ 
  436. function process(&$order) 
  437. {  
  438. //check for initial payment 
  439. if(floatval($order->InitialPayment) == 0) 
  440. //just subscribe 
  441. return $this->subscribe($order); 
  442. else 
  443. //charge then subscribe 
  444. if($this->charge($order)) 
  445. if(pmpro_isLevelRecurring($order->membership_level)) 
  446. {  
  447. if($this->subscribe($order)) 
  448. //yay! 
  449. return true; 
  450. else 
  451. //try to refund initial charge 
  452. return false; 
  453. else 
  454. //only a one time charge 
  455. $order->status = "success"; //saved on checkout page  
  456. return true; 
  457. else 
  458. {  
  459. if(empty($order->error)) { 
  460.  
  461. if ( !self::$is_loaded ) { 
  462.  
  463. $order->error = __("Payment error: Please contact the webmaster (braintree-load-error)", "paid-memberships-pro"); 
  464.  
  465. } else { 
  466.  
  467. $order->error = __( "Unknown error: Initial payment failed.", "paid-memberships-pro" ); 
  468.  
  469. return false; 
  470. }  
  471. }  
  472.  
  473. function charge(&$order) 
  474. if ( ! self::$is_loaded ) { 
  475.  
  476. $order->error = __("Payment error: Please contact the webmaster (braintree-load-error)", "paid-memberships-pro"); 
  477. return false; 
  478.  
  479. //create a code for the order 
  480. if(empty($order->code)) 
  481. $order->code = $order->getRandomCode(); 
  482.  
  483. //what amount to charge?  
  484. $amount = $order->InitialPayment; 
  485.  
  486. //tax 
  487. $order->subtotal = $amount; 
  488. $tax = $order->getTax(true); 
  489. $amount = round((float)$order->subtotal + (float)$tax, 2); 
  490.  
  491. //create a customer 
  492. $this->getCustomer($order); 
  493. if(empty($this->customer) || !empty($order->error)) 
  494. {  
  495. //failed to create customer 
  496. return false; 
  497. }  
  498.  
  499. //charge 
  500. try 
  501. {  
  502. $response = Braintree_Transaction::sale(array( 
  503. 'amount' => $amount,  
  504. 'customerId' => $this->customer->id  
  505. ));  
  506. catch (Exception $e) 
  507. //$order->status = "error"; 
  508. $order->errorcode = true; 
  509. $order->error = "Error: " . $e->getMessage() . " (" . get_class($e) . ")"; 
  510. $order->shorterror = $order->error; 
  511. return false; 
  512.  
  513. if($response->success) 
  514. //successful charge  
  515. $transaction_id = $response->transaction->id; 
  516. $response = Braintree_Transaction::submitForSettlement($transaction_id); 
  517. if($response->success) 
  518. $order->payment_transaction_id = $transaction_id;  
  519. $order->updateStatus("success");  
  520. return true;  
  521. else 
  522. {  
  523. $order->errorcode = true; 
  524. $order->error = __("Error during settlement:", 'paid-memberships-pro' ) . " " . $response->message; 
  525. $order->shorterror = $response->message; 
  526. return false; 
  527. }  
  528. else 
  529. //$order->status = "error"; 
  530. $order->errorcode = true; 
  531. $order->error = __("Error during charge:", 'paid-memberships-pro' ) . " " . $response->message; 
  532. $order->shorterror = $response->message; 
  533. return false; 
  534. }  
  535.  
  536. /** 
  537. This function will return a Braintree customer object.  
  538. If $this->customer is set, it returns it. 
  539. It first checks if the order has a subscription_transaction_id. If so, that's the customer id. 
  540. If not, it checks for a user_id on the order and searches for a customer id in the user meta. 
  541. If a customer id is found, it checks for a customer through the Braintree API. 
  542. If a customer is found and there is an AccountNumber on the order passed, it will update the customer. 
  543. If no customer is found and there is an AccountNumber on the order passed, it will create a customer. 
  544. */ 
  545. function getCustomer(&$order, $force = false) 
  546. if ( ! self::$is_loaded ) { 
  547. $order->error = __("Payment error: Please contact the webmaster (braintree-load-error)", 'paid-memberships-pro'); 
  548. return false; 
  549.  
  550. global $current_user; 
  551.  
  552. //already have it? 
  553. if(!empty($this->customer) && !$force) 
  554. return $this->customer; 
  555.  
  556. //try based on user id  
  557. if(!empty($order->user_id)) 
  558. $user_id = $order->user_id; 
  559.  
  560. //if no id passed, check the current user 
  561. if(empty($user_id) && !empty($current_user->ID)) 
  562. $user_id = $current_user->ID; 
  563.  
  564. //check for a braintree customer id 
  565. if(!empty($user_id)) 
  566. {  
  567. $customer_id = get_user_meta($user_id, "pmpro_braintree_customerid", true);  
  568.  
  569. //check for an existing Braintree customer 
  570. if(!empty($customer_id)) 
  571. try  
  572. $this->customer = Braintree_Customer::find($customer_id); 
  573.  
  574. //update the customer address, description and card 
  575. if(!empty($order->accountnumber)) 
  576. {  
  577. //put data in array for Braintree API calls 
  578. $update_array = array( 
  579. 'firstName' => $order->FirstName,  
  580. 'lastName' => $order->LastName,  
  581. 'creditCard' => array( 
  582. 'number' => $order->braintree->number,  
  583. 'expirationDate' => $order->braintree->expiration_date,  
  584. 'cardholderName' => trim($order->FirstName . " " . $order->LastName),  
  585. 'options' => array( 
  586. 'updateExistingToken' => $this->customer->creditCards[0]->token 
  587. ); 
  588.  
  589. //address too? 
  590. if(!empty($order->billing)) 
  591. //make sure Address2 is set 
  592. if(!isset($order->Address2)) 
  593. $order->Address2 = ''; 
  594.  
  595. //add billing address to array 
  596. $update_array['creditCard']['billingAddress'] = array(  
  597. 'firstName' => $order->FirstName,  
  598. 'lastName' => $order->LastName,  
  599. 'streetAddress' => $order->Address1,  
  600. 'extendedAddress' => $order->Address2,  
  601. 'locality' => $order->billing->city,  
  602. 'region' => $order->billing->state,  
  603. 'postalCode' => $order->billing->zip,  
  604. 'countryCodeAlpha2' => $order->billing->country,  
  605. 'options' => array( 
  606. 'updateExisting' => true 
  607. ); 
  608.  
  609. //update 
  610. $response = Braintree_Customer::update($customer_id, $update_array); 
  611.  
  612. if($response->success) 
  613. {  
  614. $this->customer = $response->customer; 
  615. return $this->customer; 
  616. else 
  617. {  
  618. $order->error = __("Failed to update customer.", 'paid-memberships-pro' ) . " " . $response->message; 
  619. $order->shorterror = $order->error; 
  620. return false; 
  621.  
  622. return $this->customer; 
  623. }  
  624. catch (Exception $e)  
  625. //assume no customer found  
  626. }  
  627.  
  628. //no customer id, create one 
  629. if(!empty($order->accountnumber)) 
  630. try 
  631. {  
  632. $result = Braintree_Customer::create(array( 
  633. 'firstName' => $order->FirstName,  
  634. 'lastName' => $order->LastName,  
  635. 'email' => $order->Email,  
  636. 'phone' => $order->billing->phone,  
  637. 'creditCard' => array( 
  638. 'number' => $order->braintree->number,  
  639. 'expirationDate' => $order->braintree->expiration_date,  
  640. 'cvv' => $order->braintree->cvv,  
  641. 'cardholderName' => trim($order->FirstName . " " . $order->LastName),  
  642. 'billingAddress' => array( 
  643. 'firstName' => $order->FirstName,  
  644. 'lastName' => $order->LastName,  
  645. 'streetAddress' => $order->Address1,  
  646. 'extendedAddress' => $order->Address2,  
  647. 'locality' => $order->billing->city,  
  648. 'region' => $order->billing->state,  
  649. 'postalCode' => $order->billing->zip,  
  650. 'countryCodeAlpha2' => $order->billing->country 
  651. )); 
  652.  
  653. if($result->success) 
  654. $this->customer = $result->customer; 
  655. else 
  656. {  
  657. $order->error = __("Failed to create customer.", 'paid-memberships-pro' ) . " " . $result->message; 
  658. $order->shorterror = $order->error; 
  659. return false; 
  660. }  
  661. catch (Exception $e) 
  662. {  
  663. $order->error = __("Error creating customer record with Braintree:", 'paid-memberships-pro' ) . $e->getMessage() . " (" . get_class($e) . ")"; 
  664. $order->shorterror = $order->error; 
  665. return false; 
  666.  
  667. //if we have no user id, we need to set the customer id after the user is created 
  668. if(empty($user_id)) 
  669. global $pmpro_braintree_customerid; 
  670. $pmpro_braintree_customerid = $this->customer->id; 
  671. add_action('user_register', array('PMProGateway_braintree', 'user_register')); 
  672. else 
  673. update_user_meta($user_id, "pmpro_braintree_customerid", $this->customer->id); 
  674.  
  675. return $this->customer; 
  676.  
  677. return false;  
  678.  
  679. function subscribe(&$order) 
  680. if ( ! self::$is_loaded ) { 
  681. $order->error = __("Payment error: Please contact the webmaster (braintree-load-error)", 'paid-memberships-pro'); 
  682. return false; 
  683.  
  684. //create a code for the order 
  685. if(empty($order->code)) 
  686. $order->code = $order->getRandomCode(); 
  687.  
  688. //set up customer 
  689. $this->getCustomer($order); 
  690. if(empty($this->customer) || !empty($order->error)) 
  691. return false; //error retrieving customer 
  692.  
  693. //figure out the amounts 
  694. $amount = $order->PaymentAmount; 
  695. $amount_tax = $order->getTaxForPrice($amount);  
  696. $amount = round((float)$amount + (float)$amount_tax, 2); 
  697.  
  698. /** 
  699. There are two parts to the trial. Part 1 is simply the delay until the first payment 
  700. since we are doing the first payment as a separate transaction. 
  701. The second part is the actual "trial" set by the admin. 
  702.  
  703. Braintree only supports Year or Month for billing periods, but we account for Days and Weeks just in case. 
  704. */ 
  705. //figure out the trial length (first payment handled by initial charge)  
  706. if($order->BillingPeriod == "Year") 
  707. $trial_period_days = $order->BillingFrequency * 365; //annual 
  708. elseif($order->BillingPeriod == "Day") 
  709. $trial_period_days = $order->BillingFrequency * 1; //daily 
  710. elseif($order->BillingPeriod == "Week") 
  711. $trial_period_days = $order->BillingFrequency * 7; //weekly 
  712. else 
  713. $trial_period_days = $order->BillingFrequency * 30; //assume monthly 
  714.  
  715. //convert to a profile start date 
  716. $order->ProfileStartDate = date_i18n("Y-m-d", strtotime("+ " . $trial_period_days . " Day", current_time("timestamp"))) . "T0:0:0"; 
  717.  
  718. //filter the start date 
  719. $order->ProfileStartDate = apply_filters("pmpro_profile_start_date", $order->ProfileStartDate, $order);  
  720.  
  721. //convert back to days 
  722. $trial_period_days = ceil(abs(strtotime(date_i18n("Y-m-d")) - strtotime($order->ProfileStartDate, current_time("timestamp"))) / 86400); 
  723.  
  724. //now add the actual trial set by the site 
  725. if(!empty($order->TrialBillingCycles))  
  726. $trialOccurrences = (int)$order->TrialBillingCycles; 
  727. if($order->BillingPeriod == "Year") 
  728. $trial_period_days = $trial_period_days + (365 * $order->BillingFrequency * $trialOccurrences); //annual 
  729. elseif($order->BillingPeriod == "Day") 
  730. $trial_period_days = $trial_period_days + (1 * $order->BillingFrequency * $trialOccurrences); //daily 
  731. elseif($order->BillingPeriod == "Week") 
  732. $trial_period_days = $trial_period_days + (7 * $order->BillingFrequency * $trialOccurrences); //weekly 
  733. else 
  734. $trial_period_days = $trial_period_days + (30 * $order->BillingFrequency * $trialOccurrences); //assume monthly  
  735. }  
  736.  
  737. //subscribe to the plan 
  738. try 
  739. {  
  740. $details = array( 
  741. 'paymentMethodToken' => $this->customer->creditCards[0]->token,  
  742. 'planId' => 'pmpro_' . $order->membership_id,  
  743. 'price' => $amount  
  744. ); 
  745.  
  746. if(!empty($trial_period_days)) 
  747. $details['trialPeriod'] = true; 
  748. $details['trialDuration'] = $trial_period_days; 
  749. $details['trialDurationUnit'] = "day"; 
  750.  
  751. if(!empty($order->TotalBillingCycles)) 
  752. $details['numberOfBillingCycles'] = $order->TotalBillingCycles; 
  753.  
  754. $result = Braintree_Subscription::create($details);  
  755. catch (Exception $e) 
  756. {  
  757. $order->error = __("Error subscribing customer to plan with Braintree:", 'paid-memberships-pro' ) . " " . $e->getMessage() . " (" . get_class($e) . ")"; 
  758. //return error 
  759. $order->shorterror = $order->error; 
  760. return false; 
  761.  
  762. if($result->success) 
  763. {  
  764. //if we got this far, we're all good  
  765. $order->status = "success";  
  766. $order->subscription_transaction_id = $result->subscription->id; 
  767. return true; 
  768. else 
  769. $order->error = __("Failed to subscribe with Braintree:", 'paid-memberships-pro' ) . " " . $result->message; 
  770. $order->shorterror = $result->message; 
  771. return false; 
  772. }  
  773. }  
  774.  
  775. function update(&$order) 
  776. if ( ! self::$is_loaded ) { 
  777. $order->error = __("Payment error: Please contact the webmaster (braintree-load-error)", 'paid-memberships-pro'); 
  778. return false; 
  779.  
  780. //we just have to run getCustomer which will look for the customer and update it with the new token 
  781. $this->getCustomer($order, true); 
  782.  
  783. if(!empty($this->customer) && empty($order->error)) 
  784. return true; 
  785. }  
  786. else 
  787. {  
  788. return false; //couldn't find the customer 
  789.  
  790. function cancel(&$order) 
  791. if ( ! self::$is_loaded ) { 
  792. $order->error = __("Payment error: Please contact the webmaster (braintree-load-error)", 'paid-memberships-pro'); 
  793. return false; 
  794.  
  795. //require a subscription id 
  796. if(empty($order->subscription_transaction_id)) 
  797. return false; 
  798.  
  799. //find the customer  
  800. if(!empty($order->subscription_transaction_id)) 
  801. //cancel 
  802. try  
  803. {  
  804. $result = Braintree_Subscription::cancel($order->subscription_transaction_id); 
  805. catch(Exception $e) 
  806. $order->updateStatus("cancelled"); //assume it's been cancelled already 
  807. $order->error = __("Could not find the subscription.", 'paid-memberships-pro' ) . " " . $e->getMessage(); 
  808. $order->shorterror = $order->error; 
  809. return false; //no subscription found  
  810.  
  811. if($result->success) 
  812. $order->updateStatus("cancelled");  
  813. return true; 
  814. else 
  815. $order->updateStatus("cancelled"); //assume it's been cancelled already 
  816. $order->error = __("Could not find the subscription.", 'paid-memberships-pro' ) . " " . $result->message; 
  817. $order->shorterror = $order->error; 
  818. return false; //no subscription found  
  819. else 
  820. $order->error = __("Could not find the subscription.", 'paid-memberships-pro' ); 
  821. $order->shorterror = $order->error; 
  822. return false; //no customer found 
  823. }  
  824.  
  825. /** 
  826. Save Braintree customer id after the user is registered. 
  827. */ 
  828. static function user_register($user_id) 
  829. global $pmpro_braintree_customerid; 
  830. if(!empty($pmpro_braintree_customerid)) 
  831. update_user_meta($user_id, 'pmpro_braintree_customerid', $pmpro_braintree_customerid);