/app/gateway/authorize/class-ms-gateway-authorize.php

  1. <?php 
  2. /** 
  3. * @copyright Incsub (http://incsub.com/) 
  4. * 
  5. * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0) 
  6. * 
  7. * This program is free software; you can redistribute it and/or modify 
  8. * it under the terms of the GNU General Public License, version 2, as 
  9. * published by the Free Software Foundation. 
  10. * 
  11. * This program is distributed in the hope that it will be useful,  
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of 
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  14. * GNU General Public License for more details. 
  15. * 
  16. * You should have received a copy of the GNU General Public License 
  17. * along with this program; if not, write to the Free Software 
  18. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,  
  19. * MA 02110-1301 USA 
  20. * 
  21. */ 
  22.  
  23. /** 
  24. * Authorize.net Gateway. 
  25. * 
  26. * Must use SSL to send card info to gateway. 
  27. * Integrate Authorize.net gateway send payment requests. 
  28. * 
  29. * Persisted by parent class MS_Model_Option. Singleton. 
  30. * 
  31. * @since 1.0.0 
  32. * @package Membership2 
  33. * @subpackage Model 
  34. */ 
  35. class MS_Gateway_Authorize extends MS_Gateway { 
  36.  
  37. const ID = 'authorize'; 
  38.  
  39. /** 
  40. * Gateway singleton instance. 
  41. * 
  42. * @since 1.0.0 
  43. * @var string $instance 
  44. */ 
  45. public static $instance; 
  46.  
  47. /** 
  48. * Authorize.net's Customer Information Manager wrapper. 
  49. * 
  50. * @since 1.0.0 
  51. * @var string $cim 
  52. */ 
  53. protected static $cim; 
  54.  
  55. /** 
  56. * Authorize.net API login IP. 
  57. * 
  58. * @see @link https://www.authorize.net/support/CP/helpfiles/Account/Settings/Security_Settings/General_Settings/API_Login_ID_and_Transaction_Key.htm 
  59. * @since 1.0.0 
  60. * @var string $api_login_id 
  61. */ 
  62. protected $api_login_id; 
  63.  
  64. /** 
  65. * Authorize.net API transaction key. 
  66. * 
  67. * @since 1.0.0 
  68. * @var string $api_transaction_key 
  69. */ 
  70. protected $api_transaction_key; 
  71.  
  72. /** 
  73. * Authorize.net custom log file. 
  74. * 
  75. * @since 1.0.0 
  76. * @var string $log_file 
  77. */ 
  78. protected $log_file; 
  79.  
  80.  
  81. /** 
  82. * Initialize the object. 
  83. * 
  84. * @since 1.0.0 
  85. */ 
  86. public function after_load() { 
  87. parent::after_load(); 
  88.  
  89. $this->id = self::ID; 
  90. $this->name = __( 'Authorize.net Gateway', MS_TEXT_DOMAIN ); 
  91. $this->group = 'Authorize.net'; 
  92. $this->manual_payment = false; 
  93. $this->pro_rate = true; 
  94.  
  95. /** 
  96. * Processes purchase action. 
  97. * 
  98. * This function is called when a payment was made: We check if the 
  99. * transaction was successful. If it was we call `$invoice->changed()` which 
  100. * will update the membership status accordingly. 
  101. * 
  102. * @since 1.0.0 
  103. * @param MS_Model_Relationship $subscription The related membership relationship. 
  104. */ 
  105. public function process_purchase( $subscription ) { 
  106. do_action( 
  107. 'ms_gateway_authorize_process_purchase_before',  
  108. $subscription,  
  109. $this 
  110. ); 
  111.  
  112. if ( ! is_ssl() ) { 
  113. throw new Exception( __( 'You must use HTTPS in order to do this', 'membership' ) ); 
  114.  
  115. $invoice = $subscription->get_current_invoice(); 
  116. $member = $subscription->get_member(); 
  117.  
  118. // manage authorize customer profile 
  119. $cim_profile_id = $this->get_cim_profile_id( $member ); 
  120.  
  121. if ( empty( $cim_profile_id ) ) { 
  122. $this->create_cim_profile( $member ); 
  123. } elseif ( $cim_payment_profile_id = trim( filter_input( INPUT_POST, 'profile' ) ) ) { 
  124. // Fetch for user selected cim profile 
  125. $response = $this->get_cim()->getCustomerPaymentProfile( $cim_profile_id, $cim_payment_profile_id ); 
  126.  
  127. if ( $response->isError() ) { 
  128. throw new Exception( 
  129. __( 'The selected payment profile is invalid, enter a new credit card', MS_TEXT_DOMAIN ) 
  130. ); 
  131. } else { 
  132. $this->update_cim_profile( $member ); 
  133.  
  134. if ( ! $invoice->is_paid() ) { 
  135. // Not paid yet, request the transaction. 
  136. $this->online_purchase( $invoice, $member ); 
  137. } elseif ( 0 == $invoice->total ) { 
  138. // Paid and free. 
  139. $invoice->changed(); 
  140.  
  141. return apply_filters( 
  142. 'ms_gateway_authorize_process_purchase',  
  143. $invoice,  
  144. $this 
  145. ); 
  146.  
  147. /** 
  148. * Request automatic payment to the gateway. 
  149. * 
  150. * @since 1.0.0 
  151. * @param MS_Model_Relationship $subscription The related membership relationship. 
  152. * @return bool True on success. 
  153. */ 
  154. public function request_payment( $subscription ) { 
  155. $was_paid = false; 
  156.  
  157. do_action( 
  158. 'ms_gateway_authorize_request_payment_before',  
  159. $subscription,  
  160. $this 
  161. ); 
  162.  
  163. $member = $subscription->get_member(); 
  164. $invoice = $subscription->get_current_invoice(); 
  165.  
  166. if ( ! $invoice->is_paid() ) { 
  167. // Not paid yet, request the transaction. 
  168. try { 
  169. $was_paid = $this->online_purchase( $invoice, $member ); 
  170. catch( Exception $e ) { 
  171. MS_Model_Event::save_event( MS_Model_Event::TYPE_PAYMENT_FAILED, $subscription ); 
  172. MS_Helper_Debug::log( $e->getMessage() ); 
  173. } else { 
  174. // Invoice was already paid earlier. 
  175. $was_paid = true; 
  176.  
  177. do_action( 
  178. 'ms_gateway_authorize_request_payment_after',  
  179. $subscription,  
  180. $was_paid,  
  181. $this 
  182. ); 
  183.  
  184. return $was_paid; 
  185.  
  186. /** 
  187. * Processes online payments. 
  188. * 
  189. * Send to Authorize.net to process the payment immediatly. 
  190. * 
  191. * @since 1.0.0 
  192. * @param MS_Model_Invoice $invoice The invoice to pay. 
  193. * @param MS_Model_Member The member paying the invoice. 
  194. * @return bool True on success, otherwise throws an exception. 
  195. */ 
  196. protected function online_purchase( &$invoice, $member ) { 
  197. do_action( 
  198. 'ms_gateway_authorize_online_purchase_before',  
  199. $invoice,  
  200. $member,  
  201. $this 
  202. ); 
  203.  
  204. if ( 0 == $invoice->total ) { 
  205. $invoice->pay_it( MS_Gateway_Free::ID, '' ); 
  206. $invoice->add_notes( __( 'Total is zero. Payment approved. Not sent to gateway.', MS_TEXT_DOMAIN ) ); 
  207. $invoice->save(); 
  208. $invoice->changed(); 
  209. return $invoice; 
  210. $amount = MS_Helper_Billing::format_price( $invoice->total ); 
  211.  
  212. if ( $this->mode == self::MODE_SANDBOX ) { 
  213. $invoice->add_notes( __( 'Sandbox', MS_TEXT_DOMAIN ) ); 
  214.  
  215. $cim_transaction = $this->get_cim_transaction( $member ); 
  216. $cim_transaction->amount = $amount; 
  217. $cim_transaction->order->invoiceNumber = $invoice->id; 
  218.  
  219. $invoice->timestamp = time(); 
  220. $invoice->save(); 
  221.  
  222. $response = $this->get_cim()->createCustomerProfileTransaction( 
  223. 'AuthCapture',  
  224. $cim_transaction 
  225. ); 
  226.  
  227. if ( $response->isOk() ) { 
  228. $transaction_response = $response->getTransactionResponse(); 
  229.  
  230. if ( $transaction_response->approved ) { 
  231. $external_id = $response->getTransactionResponse()->transaction_id; 
  232. $invoice->pay_it( $this->id, $external_id ); 
  233. } else { 
  234. throw new Exception( 
  235. sprintf( 
  236. __( 'Payment Failed: code %s, subcode %s, reason code %, reason %s', MS_TEXT_DOMAIN ),  
  237. $transaction_response->response_code,  
  238. $transaction_response->response_subcode,  
  239. $transaction_response->response_reason_code,  
  240. $transaction_response->response_reason 
  241. ); 
  242. } else { 
  243. throw new Exception( 
  244. __( 'Payment Failed: ', MS_TEXT_DOMAIN ) . $response->getMessageText() 
  245. ); 
  246.  
  247. $invoice = apply_filters( 
  248. 'ms_gateway_authorize_online_purchase_invoice',  
  249. $invoice,  
  250. $member,  
  251. $this 
  252. ); 
  253.  
  254. return true; 
  255.  
  256. /** 
  257. * Save card info. 
  258. * 
  259. * Save only 4 last digits and expire date. 
  260. * 
  261. * @since 1.0.0 
  262. * @param MS_Model_Member $member The member to save card info. 
  263. */ 
  264. public function save_card_info( $member ) { 
  265. $cim_profile_id = $this->get_cim_profile_id( $member ); 
  266. $cim_payment_profile_id = $this->get_cim_payment_profile_id( $member ); 
  267. $profile = $this->get_cim_profile( $member ); 
  268.  
  269. if ( ! empty( $profile['customerPaymentProfileId'] ) 
  270. && $cim_payment_profile_id == $profile['customerPaymentProfileId'] 
  271. ) { 
  272. $exp_year = filter_input( INPUT_POST, 'exp_year', FILTER_VALIDATE_INT ); 
  273. $exp_month = substr( filter_input( INPUT_POST, 'exp_month', FILTER_VALIDATE_INT ), -2 ); 
  274. $member->set_gateway_profile( 
  275. $this->id,  
  276. 'card_exp',  
  277. gmdate( 'Y-m-t', strtotime( "{$exp_year}-{$exp_month}-01" ) ) 
  278. ); 
  279.  
  280. $member->set_gateway_profile( 
  281. $this->id,  
  282. 'card_num',  
  283. str_replace( 'XXXX', '', $profile['payment']['creditCard']['cardNumber'] ) 
  284. ); 
  285.  
  286. do_action( 
  287. 'ms_gateway_authorize_save_card_info_after',  
  288. $member,  
  289. $this 
  290. ); 
  291.  
  292. /** 
  293. * Loads Authorize.net lib. 
  294. * 
  295. * @since 1.0.0 
  296. */ 
  297. protected function load_authorize_lib() { 
  298. do_action( 'ms_gateway_authorize_load_authorize_lib', $this ); 
  299.  
  300. require_once MS_Plugin::instance()->dir . '/lib/authorize.net/autoload.php'; 
  301.  
  302. /** 
  303. * Returns the instance of AuthorizeNetCIM class. 
  304. * 
  305. * @since 1.0.0 
  306. * 
  307. * @return AuthorizeNetCIM The instance of AuthorizeNetCIM class. 
  308. */ 
  309. protected function get_cim() { 
  310. $cim = null; 
  311.  
  312. if ( ! empty( self::$cim ) ) { 
  313. $cim = self::$cim; 
  314. } else { 
  315. $this->load_authorize_lib(); 
  316.  
  317. $cim = new AuthorizeNetCIM( $this->api_login_id, $this->api_transaction_key ); 
  318. $cim->setSandbox( $this->mode != self::MODE_LIVE ); 
  319.  
  320. if ( $this->log_file ) { 
  321. $cim->setLogFile( $this->log_file ); 
  322. self::$cim = $cim; 
  323.  
  324. return apply_filters( 
  325. 'ms_gateway_authorize_get_cim',  
  326. $cim,  
  327. $this 
  328. ); 
  329.  
  330. /** 
  331. * Get saved customer information manager profile id. 
  332. * 
  333. * @since 1.0.0 
  334. * @param int $user_id The user Id. 
  335. * @return string The CIM profile Id. 
  336. */ 
  337. public function get_cim_profile_id( $member ) { 
  338. $cim_profile_id = $member->get_gateway_profile( 
  339. $this->id,  
  340. 'cim_profile_id' 
  341. ); 
  342.  
  343. return apply_filters( 
  344. 'ms_gateway_authorize_get_cim_profile_id',  
  345. $cim_profile_id,  
  346. $member,  
  347. $this 
  348. ); 
  349.  
  350. /** 
  351. * Get saved customer information manager payment profile id. 
  352. * 
  353. * @since 1.0.0 
  354. * @param int $user_id The user Id. 
  355. * @return string The CIM payment profile Id. 
  356. */ 
  357. public function get_cim_payment_profile_id( $member ) { 
  358. $cim_payment_profile_id = $member->get_gateway_profile( 
  359. $this->id,  
  360. 'cim_payment_profile_id' 
  361. ); 
  362.  
  363. return apply_filters( 
  364. 'ms_gateway_authorize_get_cim_payment_profile_id',  
  365. $cim_payment_profile_id,  
  366. $member,  
  367. $this 
  368. ); 
  369.  
  370. /** 
  371. * Save CIM profile IDs. 
  372. * 
  373. * @since 1.0.0 
  374. * @param MS_Model_Member $member The member to save CIM IDs. 
  375. * @param string $cim_profile_id The CIM profile ID to save. 
  376. * @param string $cim_payment_profile_id The CIM payment profile ID to save. 
  377. */ 
  378. protected function save_cim_profile( $member, $cim_profile_id, $cim_payment_profile_id ) { 
  379. $member->set_gateway_profile( 
  380. $this->id,  
  381. 'cim_profile_id',  
  382. $cim_profile_id 
  383. ); 
  384. $member->set_gateway_profile( 
  385. $this->id,  
  386. 'cim_payment_profile_id',  
  387. $cim_payment_profile_id 
  388. ); 
  389.  
  390. $this->save_card_info( $member ); 
  391. $member->save(); 
  392.  
  393. do_action( 
  394. 'ms_gateway_authorize_save_cim_profile',  
  395. $member,  
  396. $cim_profile_id,  
  397. $cim_payment_profile_id,  
  398. $this 
  399. ); 
  400.  
  401. /** 
  402. * Get customer information manager profile from Authorize.net. 
  403. * 
  404. * @since 1.0.0 
  405. * 
  406. * @param MS_Model_Member $member The member. 
  407. * @return array The A.net payment profiles array structure. 
  408. */ 
  409. public function get_cim_profile( $member ) { 
  410. $cim_profiles = array(); 
  411. $cim_profile_id = $this->get_cim_profile_id( $member ); 
  412.  
  413. if ( $cim_profile_id ) { 
  414. $response = $this->get_cim()->getCustomerProfile( $cim_profile_id ); 
  415.  
  416. if ( $response->isOk() ) { 
  417. $cim_profiles = json_decode( json_encode( $response->xml->profile ), true ); 
  418.  
  419. if ( is_array( $cim_profiles ) 
  420. && ! empty( $cim_profiles['paymentProfiles'] ) 
  421. && is_array( $cim_profiles['paymentProfiles'] ) 
  422. ) { 
  423. $cim_profiles = $cim_profiles['paymentProfiles']; 
  424.  
  425. return apply_filters( 
  426. 'ms_gateway_authorize_get_cim_profile',  
  427. $cim_profiles,  
  428. $this 
  429. ); 
  430.  
  431. /** 
  432. * Creates Authorize.net CIM profile for current user. 
  433. * 
  434. * @since 1.0.0 
  435. * @param MS_Model_Member $member The member to create CIM profile to. 
  436. */ 
  437. protected function create_cim_profile( $member ) { 
  438. do_action( 
  439. 'ms_gateway_authorize_create_cim_profile_before',  
  440. $member,  
  441. $this 
  442. ); 
  443.  
  444. $this->load_authorize_lib(); 
  445. $customer = new AuthorizeNetCustomer(); 
  446. $customer->merchantCustomerId = $member->id; 
  447. $customer->email = $member->email; 
  448. $customer->paymentProfiles[] = $this->create_cim_payment_profile(); 
  449.  
  450. $response = $this->get_cim()->createCustomerProfile( $customer ); 
  451.  
  452. if ( $response->isError() ) { 
  453. MS_Helper_Debug::log( $response ); 
  454.  
  455. // Duplicate record, delete the old one. 
  456. if ( 'E00039' == $response->xml->messages->message->code ) { 
  457. $cim_profile_id = str_replace( 'A duplicate record with ID ', '', $response->xml->messages->message->text ); 
  458. $cim_profile_id = (int) str_replace( ' already exists.', '', $cim_profile_id ); 
  459.  
  460. $this->get_cim()->deleteCustomerProfile( $cim_profile_id ); 
  461.  
  462. // Try again 
  463. $this->create_cim_profile( $member ); 
  464. return; 
  465. } else { 
  466. throw new Exception( 
  467. __( 'Payment failed due to CIM profile not created: ', MS_TEXT_DOMAIN ) . $response->getMessageText() 
  468. ); 
  469.  
  470. $cim_profile_id = $response->getCustomerProfileId(); 
  471. $cim_payment_profile_id = $response->getCustomerPaymentProfileIds(); 
  472. $this->save_cim_profile( $member, $cim_profile_id, $cim_payment_profile_id ); 
  473.  
  474. /** 
  475. * Updates CIM profile by adding a new credit card. 
  476. * 
  477. * @since 1.0.0 
  478. * @param MS_Model_Member $member The member to update CIM profile. 
  479. */ 
  480. public function update_cim_profile( $member ) { 
  481. do_action( 
  482. 'ms_gateway_authorize_update_cim_profile_before',  
  483. $member,  
  484. $this 
  485. ); 
  486.  
  487. $cim_profile_id = $this->get_cim_profile_id( $member ); 
  488. $cim_payment_profile_id = $this->get_cim_payment_profile_id( $member ); 
  489.  
  490. if ( empty( $cim_payment_profile_id ) ) { 
  491. $response = $this->get_cim()->createCustomerPaymentProfile( 
  492. $cim_profile_id,  
  493. $this->create_cim_payment_profile() 
  494. ); 
  495. $cim_payment_profile_id = $response->getCustomerPaymentProfileIds(); 
  496. } else { 
  497. $response = $this->get_cim()->updateCustomerPaymentProfile( 
  498. $cim_profile_id,  
  499. $cim_payment_profile_id,  
  500. self::create_cim_payment_profile() 
  501. ); 
  502.  
  503. // If the error is not due to a duplicate customer payment profile. 
  504. if ( $response->isError() && 'E00039' != $response->xml->messages->message->code ) { 
  505. throw new Exception( 
  506. __( 'Payment failed due to CIM profile not updated: ', MS_TEXT_DOMAIN ) . $response->getMessageText() 
  507. ); 
  508.  
  509. $this->save_cim_profile( $member, $cim_profile_id, $cim_payment_profile_id ); 
  510.  
  511. /** 
  512. * Creates CIM payment profile and fills it with posted credit card data. 
  513. * 
  514. * @since 1.0.0 
  515. * @return AuthorizeNetPaymentProfile The instance of AuthorizeNetPaymentProfile class. 
  516. */ 
  517. protected function create_cim_payment_profile() { 
  518. $this->load_authorize_lib(); 
  519.  
  520. $payment = new AuthorizeNetPaymentProfile(); 
  521.  
  522. // billing information 
  523. $payment->billTo->firstName = substr( trim( filter_input( INPUT_POST, 'first_name' ) ), 0, 50 ); 
  524. $payment->billTo->lastName = substr( trim( filter_input( INPUT_POST, 'last_name' ) ), 0, 50 ); 
  525. $payment->billTo->company = substr( trim( filter_input( INPUT_POST, 'company' ) ), 0, 50 ); 
  526. $payment->billTo->address = substr( trim( filter_input( INPUT_POST, 'address' ) ), 0, 60 ); 
  527. $payment->billTo->city = substr( trim( filter_input( INPUT_POST, 'city' ) ), 0, 40 ); 
  528. $payment->billTo->state = substr( trim( filter_input( INPUT_POST, 'state' ) ), 0, 40 ); 
  529. $payment->billTo->zip = substr( trim( filter_input( INPUT_POST, 'zip' ) ), 0, 20 ); 
  530. $payment->billTo->country = substr( trim( filter_input( INPUT_POST, 'country' ) ), 0, 60 ); 
  531. $payment->billTo->phoneNumber = substr( trim( filter_input( INPUT_POST, 'phone' ) ), 0, 25 ); 
  532.  
  533. // card information 
  534. $payment->payment->creditCard->cardNumber = preg_replace( '/\D/', '', filter_input( INPUT_POST, 'card_num' ) ); 
  535. $payment->payment->creditCard->cardCode = trim( filter_input( INPUT_POST, 'card_code' ) ); 
  536. $payment->payment->creditCard->expirationDate = sprintf( 
  537. '%04d-%02d',  
  538. filter_input( INPUT_POST, 'exp_year', FILTER_VALIDATE_INT ),  
  539. substr( filter_input( INPUT_POST, 'exp_month', FILTER_VALIDATE_INT ), -2 ) 
  540. ); 
  541.  
  542. return apply_filters( 
  543. 'ms_gateway_authorize_create_cim_payment_profile',  
  544. $payment,  
  545. $this 
  546. ); 
  547.  
  548. /** 
  549. * Initializes and returns Authorize.net CIM transaction object. 
  550. * 
  551. * @since 1.0.0 
  552. * @param MS_Model_Member $member The member. 
  553. * @return AuthorizeNetTransaction The instance of AuthorizeNetTransaction class. 
  554. */ 
  555. protected function get_cim_transaction( $member ) { 
  556. $this->load_authorize_lib(); 
  557.  
  558. $cim_profile_id = $this->get_cim_profile_id( $member ); 
  559. $cim_payment_profile_id = $this->get_cim_payment_profile_id( $member ); 
  560.  
  561. if ( empty( $cim_profile_id ) || empty( $cim_payment_profile_id ) ) { 
  562. throw new Exception( __( 'CIM Payment profile not found', MS_TEXT_DOMAIN ) ); 
  563.  
  564. $transaction = new AuthorizeNetTransaction(); 
  565. $transaction->customerProfileId = $cim_profile_id; 
  566. $transaction->customerPaymentProfileId = $cim_payment_profile_id; 
  567.  
  568. return apply_filters( 
  569. 'ms_gateway_authorize_get_cim_transaction',  
  570. $transaction,  
  571. $member,  
  572. $this 
  573. ); 
  574.  
  575. /** 
  576. * Verify required fields. 
  577. * 
  578. * @since 1.0.0 
  579. * @return boolean True if it is configured. 
  580. */ 
  581. public function is_configured() { 
  582. $is_configured = true; 
  583. $required = array( 'api_login_id', 'api_transaction_key' ); 
  584.  
  585. foreach ( $required as $field ) { 
  586. $value = $this->$field; 
  587. if ( empty( $value ) ) { 
  588. $is_configured = false; 
  589. break; 
  590.  
  591. return apply_filters( 
  592. 'ms_gateway_authorize_is_configured',  
  593. $is_configured,  
  594. $this 
  595. ); 
.