/app/gateway/stripe/class-ms-gateway-stripe-api.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. * Stripe Gateway API Integration. 
  25. * 
  26. * This object is shared between the Stripe Single and Stripe Subscription 
  27. * gateways. 
  28. * 
  29. * @since 2.0.0 
  30. * @package Membership2 
  31. * @subpackage Model 
  32. */ 
  33. class MS_Gateway_Stripe_Api extends MS_Model_Option { 
  34.  
  35. const ID = 'stripe'; 
  36.  
  37. /** 
  38. * Gateway singleton instance. 
  39. * 
  40. * @since 1.0.0 
  41. * @var string $instance 
  42. */ 
  43. public static $instance; 
  44.  
  45. /** 
  46. * Stripe test secret key (sandbox). 
  47. * 
  48. * @see https://support.stripe.com/questions/where-do-i-find-my-api-keys 
  49. * 
  50. * @since 1.0.0 
  51. * @var string $test_secret_key 
  52. */ 
  53. protected $test_secret_key; 
  54.  
  55. /** 
  56. * Stripe Secret key (live). 
  57. * 
  58. * @since 1.0.0 
  59. * @var string $secret_key 
  60. */ 
  61. protected $secret_key; 
  62.  
  63. /** 
  64. * Stripe test publishable key (sandbox). 
  65. * 
  66. * @since 1.0.0 
  67. * @var string $test_publishable_key 
  68. */ 
  69. protected $test_publishable_key; 
  70.  
  71. /** 
  72. * Stripe publishable key (live). 
  73. * 
  74. * @since 1.0.0 
  75. * @var string $publishable_key 
  76. */ 
  77. protected $publishable_key; 
  78.  
  79. /** 
  80. * Determines whether the API should use test or live keys to contact Stripe. 
  81. * 
  82. * @since 2.0.0 
  83. * @var string 
  84. */ 
  85. public $mode = ''; 
  86.  
  87. /** 
  88. * Load Stripe lib. 
  89. * 
  90. * @since 1.0.0 
  91. * @internal 
  92. */ 
  93. public function load_stripe_lib() { 
  94. require_once MS_Plugin::instance()->dir . '/lib/stripe-php/lib/Stripe.php'; 
  95.  
  96. $secret_key = $this->get_secret_key(); 
  97. Stripe::setApiKey( $secret_key ); 
  98.  
  99. do_action( 'ms_gateway_stripe_load_stripe_lib_after', $this ); 
  100.  
  101. /** 
  102. * Get Member's Stripe Customer Object, creates a new customer if not found. 
  103. * 
  104. * @since 1.0.0 
  105. * @internal 
  106. * 
  107. * @param MS_Model_Member $member The member. 
  108. * @param string $token The credit card token. 
  109. */ 
  110. public function get_stripe_customer( $member, $token ) { 
  111. $this->load_stripe_lib(); 
  112.  
  113. $customer = $this->find_customer( $member ); 
  114.  
  115. if ( empty( $customer ) ) { 
  116. $customer = Stripe_Customer::create( 
  117. array( 
  118. 'card' => $token,  
  119. 'email' => $member->email,  
  120. ); 
  121. $member->set_gateway_profile( self::ID, 'customer_id', $customer->id ); 
  122. $member->save(); 
  123. } else { 
  124. $this->add_card( $member, $customer, $token ); 
  125.  
  126. return apply_filters( 
  127. 'ms_gateway_stripe_get_stripe_customer',  
  128. $customer,  
  129. $member,  
  130. $this 
  131. ); 
  132.  
  133. /** 
  134. * Get Member's Stripe Customer Object. 
  135. * 
  136. * @since 1.0.0 
  137. * @internal 
  138. * 
  139. * @param MS_Model_Member $member The member. 
  140. */ 
  141. public function find_customer( $member ) { 
  142. $this->load_stripe_lib(); 
  143.  
  144. $customer_id = $member->get_gateway_profile( self::ID, 'customer_id' ); 
  145. $customer = null; 
  146.  
  147. if ( ! empty( $customer_id ) ) { 
  148. $customer = Stripe_Customer::retrieve( $customer_id ); 
  149.  
  150. // Seems like the customer was manually deleted on Stripe website. 
  151. if ( isset( $customer->deleted ) && $customer->deleted ) { 
  152. $customer = null; 
  153. $member->set_gateway_profile( self::ID, 'customer_id', '' ); 
  154.  
  155. return apply_filters( 
  156. 'ms_gateway_stripe_find_customer',  
  157. $customer,  
  158. $member,  
  159. $this 
  160. ); 
  161.  
  162. /** 
  163. * Add card info to Stripe customer profile and to WordPress user meta. 
  164. * 
  165. * @since 1.0.0 
  166. * @api 
  167. * 
  168. * @param MS_Model_Member $member The member model. 
  169. * @param Stripe_Customer $customer The stripe customer object. 
  170. * @param string $token The stripe card token generated by the gateway. 
  171. */ 
  172. public function add_card( $member, $customer, $token ) { 
  173. $this->load_stripe_lib(); 
  174. $card = false; 
  175.  
  176. // 1. Save card to Stripe profile. 
  177.  
  178. // Stripe API until version 2015-02-16 
  179. if ( ! empty( $customer->cards ) ) { 
  180. $card = $customer->cards->create( array( 'card' => $token ) ); 
  181. $customer->default_card = $card->id; 
  182.  
  183. // Stripe API since 2015-02-18 
  184. if ( ! empty( $customer->sources ) ) { 
  185. $card = $customer->sources->create( array( 'card' => $token ) ); 
  186. $customer->default_source = $card->id; 
  187.  
  188. if ( $card ) { 
  189. $customer->save(); 
  190.  
  191. /** 
  192. * This action is used by the Taxamo Add-on to check additional country 
  193. * evidence (CC country). 
  194. * 
  195. * @since 2.0.0 
  196. */ 
  197. do_action( 'ms_gateway_stripe_credit_card_saved', $card, $member, $this ); 
  198.  
  199. // 2. Save card to WordPress user meta. 
  200.  
  201. if ( $card ) { 
  202. $member->set_gateway_profile( 
  203. self::ID,  
  204. 'card_exp',  
  205. gmdate( 'Y-m-d', strtotime( "{$card->exp_year}-{$card->exp_month}-01" ) ) 
  206. ); 
  207. $member->set_gateway_profile( self::ID, 'card_num', $card->last4 ); 
  208. $member->save(); 
  209.  
  210. do_action( 
  211. 'ms_gateway_stripe_add_card_info_after',  
  212. $customer,  
  213. $token,  
  214. $this 
  215. ); 
  216.  
  217. /** 
  218. * Creates a one-time charge that is immediately captured. 
  219. * 
  220. * This means the money is instantly transferred to our own stripe account. 
  221. * 
  222. * @since 2.0.0 
  223. * @internal 
  224. * 
  225. * @param Stripe_Customer $customer Stripe customer to charge. 
  226. * @param float $amount Amount in currency (i.e. in USD, not in cents) 
  227. * @param string $currency 3-digit currency code. 
  228. * @param string $description This is displayed on the invoice to customer. 
  229. * @return Stripe_Charge The resulting charge object. 
  230. */ 
  231. public function charge( $customer, $amount, $currency, $description ) { 
  232. $this->load_stripe_lib(); 
  233.  
  234. $charge = Stripe_Charge::create( 
  235. array( 
  236. 'customer' => $customer->id,  
  237. 'amount' => intval( $amount * 100 ), // Amount in cents! 
  238. 'currency' => strtolower( $currency ),  
  239. 'description' => $description,  
  240. ); 
  241.  
  242. return apply_filters( 
  243. 'ms_gateway_stripe_charge',  
  244. $charge,  
  245. $customer,  
  246. $amount,  
  247. $currency,  
  248. $description,  
  249. $this 
  250. ); 
  251.  
  252. /** 
  253. * Fetches an existing subscription from Stripe and returns it. 
  254. * 
  255. * If the specified customer did not subscribe to the membership then 
  256. * boolean FALSE will be returned. 
  257. * 
  258. * @since 2.0.0 
  259. * @internal 
  260. * 
  261. * @param Stripe_Customer $customer Stripe customer to charge. 
  262. * @param MS_Model_Membership $membership The membership. 
  263. * @return Stripe_Subscription|false The resulting charge object. 
  264. */ 
  265. public function get_subscription( $customer, $membership ) { 
  266. $this->load_stripe_lib(); 
  267.  
  268. $plan_id = MS_Gateway_Stripeplan::get_the_id( 
  269. $membership->id,  
  270. 'plan' 
  271. ); 
  272.  
  273. /** 
  274. * Check all subscriptions of the customer and find the subscription 
  275. * for the specified membership. 
  276. */ 
  277. $last_checked = false; 
  278. $has_more = false; 
  279. $subscription = false; 
  280.  
  281. do { 
  282. $args = array(); 
  283. if ( $last_checked ) { 
  284. $args['starting_after'] = $last_checked; 
  285. $active_subs = $customer->subscriptions->all( $args ); 
  286. $has_more = $active_subs->has_more; 
  287.  
  288. foreach ( $active_subs->data as $sub ) { 
  289. if ( $sub->plan->id == $plan_id ) { 
  290. $subscription = $sub; 
  291. $has_more = false; 
  292. break 2; 
  293. $last_checked = $sub->id; 
  294. } while ( $has_more ); 
  295.  
  296. return apply_filters( 
  297. 'ms_gateway_stripe_get_subscription',  
  298. $subscription,  
  299. $customer,  
  300. $membership,  
  301. $this 
  302. ); 
  303.  
  304. /** 
  305. * Creates a subscription that starts immediately. 
  306. * 
  307. * @since 2.0.0 
  308. * @internal 
  309. * 
  310. * @param Stripe_Customer $customer Stripe customer to charge. 
  311. * @param MS_Model_Invoice $invoice The relevant invoice. 
  312. * @return Stripe_Subscription The resulting charge object. 
  313. */ 
  314. public function subscribe( $customer, $invoice ) { 
  315. $this->load_stripe_lib(); 
  316.  
  317. $membership = $invoice->get_membership(); 
  318. $plan_id = MS_Gateway_Stripeplan::get_the_id( 
  319. $membership->id,  
  320. 'plan' 
  321. ); 
  322.  
  323. $subscription = self::get_subscription( $customer, $membership ); 
  324.  
  325. /** 
  326. * If no active subscription was found for the membership create it. 
  327. */ 
  328. if ( ! $subscription ) { 
  329. $tax_percent = null; 
  330. $coupon_id = null; 
  331.  
  332. if ( is_numeric( $invoice->tax_rate ) && $invoice->tax_rate > 0 ) { 
  333. $tax_percent = floatval( $invoice->tax_rate ); 
  334. if ( $invoice->coupon_id ) { 
  335. $coupon_id = MS_Gateway_Stripeplan::get_the_id( 
  336. $invoice->coupon_id,  
  337. 'coupon' 
  338. ); 
  339.  
  340. $args = array( 
  341. 'plan' => $plan_id,  
  342. 'tax_percent' => $tax_percent,  
  343. 'coupon' => $coupon_id,  
  344. ); 
  345. $subscription = $customer->subscriptions->create( $args ); 
  346.  
  347. return apply_filters( 
  348. 'ms_gateway_stripe_subscribe',  
  349. $subscription,  
  350. $customer,  
  351. $invoice,  
  352. $membership,  
  353. $this 
  354. ); 
  355.  
  356. /** 
  357. * Creates or updates the payment plan specified by the function parameter. 
  358. * 
  359. * @since 2.0.0 
  360. * @internal 
  361. * 
  362. * @param array $plan_data The plan-object containing all details for Stripe. 
  363. */ 
  364. public function create_or_update_plan( $plan_data ) { 
  365. $this->load_stripe_lib(); 
  366.  
  367. $item_id = $plan_data['id']; 
  368. $all_items = MS_Factory::get_transient( 'ms_stripeplan_plans' ); 
  369. $all_items = lib2()->array->get( $all_items ); 
  370.  
  371. if ( ! isset( $all_items[$item_id] ) 
  372. || ! is_a( $all_items[$item_id], 'Stripe_Plan' ) 
  373. ) { 
  374. try { 
  375. $item = Stripe_Plan::retrieve( $item_id ); 
  376. } catch( Exception $e ) { 
  377. // If the plan does not exist then stripe will throw an Exception. 
  378. $item = false; 
  379. $all_items[$item_id] = $item; 
  380. } else { 
  381. $item = $all_items[$item_id]; 
  382.  
  383. /** 
  384. * Stripe can only update the plan-name, so we have to delete and 
  385. * recreate the plan manually. 
  386. */ 
  387. if ( $item && is_a( $item, 'Stripe_Plan' ) ) { 
  388. $item->delete(); 
  389. $all_items[$item_id] = false; 
  390.  
  391. if ( $plan_data['amount'] > 0 ) { 
  392. $item = Stripe_Plan::create( $plan_data ); 
  393. $all_items[$item_id] = $item; 
  394.  
  395. MS_Factory::set_transient( 
  396. 'ms_stripeplan_plans',  
  397. $all_items,  
  398. HOUR_IN_SECONDS 
  399. ); 
  400.  
  401. /** 
  402. * Creates or updates the coupon specified by the function parameter. 
  403. * 
  404. * @since 2.0.0 
  405. * @internal 
  406. * 
  407. * @param array $coupon_data The object containing all details for Stripe. 
  408. */ 
  409. public function create_or_update_coupon( $coupon_data ) { 
  410. $this->load_stripe_lib(); 
  411.  
  412. $item_id = $coupon_data['id']; 
  413. $all_items = MS_Factory::get_transient( 'ms_stripeplan_plans' ); 
  414. $all_items = lib2()->array->get( $all_items ); 
  415.  
  416. if ( ! isset( $all_items[$item_id] ) 
  417. || ! is_a( $all_items[$item_id], 'Stripe_Coupon' ) 
  418. ) { 
  419. try { 
  420. $item = Stripe_Coupon::retrieve( $item_id ); 
  421. } catch( Exception $e ) { 
  422. // If the coupon does not exist then stripe will throw an Exception. 
  423. $item = false; 
  424. $all_items[$item_id] = $item; 
  425. } else { 
  426. $item = $all_items[$item_id]; 
  427.  
  428. /** 
  429. * Stripe can only update the coupon-name, so we have to delete and 
  430. * recreate the coupon manually. 
  431. */ 
  432. if ( $item && is_a( $item, 'Stripe_Coupon' ) ) { 
  433. $item->delete(); 
  434. $all_items[$item_id] = false; 
  435.  
  436. $item = Stripe_Coupon::create( $coupon_data ); 
  437. $all_items[$item_id] = $item; 
  438.  
  439. MS_Factory::set_transient( 
  440. 'ms_stripeplan_coupons',  
  441. $all_items,  
  442. HOUR_IN_SECONDS 
  443. ); 
  444.  
  445. /** 
  446. * Get Stripe publishable key. 
  447. * 
  448. * @since 1.0.0 
  449. * @api 
  450. * 
  451. * @return string The Stripe API publishable key. 
  452. */ 
  453. public function get_publishable_key() { 
  454. $publishable_key = null; 
  455.  
  456. if ( MS_Gateway::MODE_LIVE == $this->mode ) { 
  457. $publishable_key = $this->publishable_key; 
  458. } else { 
  459. $publishable_key = $this->test_publishable_key; 
  460.  
  461. return apply_filters( 
  462. 'ms_gateway_stripe_get_publishable_key',  
  463. $publishable_key 
  464. ); 
  465.  
  466. /** 
  467. * Get Stripe secret key. 
  468. * 
  469. * @since 1.0.0 
  470. * @internal The secret key should not be used outside this object! 
  471. * 
  472. * @return string The Stripe API secret key. 
  473. */ 
  474. public function get_secret_key() { 
  475. $secret_key = null; 
  476.  
  477. if ( MS_Gateway::MODE_LIVE == $this->mode ) { 
  478. $secret_key = $this->secret_key; 
  479. } else { 
  480. $secret_key = $this->test_secret_key; 
  481.  
  482. return apply_filters( 
  483. 'ms_gateway_stripe_get_secret_key',  
  484. $secret_key 
  485. ); 
  486.  
  487. /** 
  488. * Little hack to force the plugin to store/load the stripe_api data in same 
  489. * option-field as the stripe-gateway settings. 
  490. * 
  491. * @since 2.0.0 
  492. * @return string 
  493. */ 
  494. public function option_key() { 
  495. // Option key should be all lowercase. 
  496. $key = 'ms_gateway_stripe'; 
  497.  
  498. return $key; 
.