/app/gateway/stripe/class-ms-gateway-stripe-api.php

  1. <?php 
  2. /** 
  3. * Stripe Gateway API Integration. 
  4. * 
  5. * This object is shared between the Stripe Single and Stripe Subscription 
  6. * gateways. 
  7. * 
  8. * @since 1.0.0 
  9. * @package Membership2 
  10. * @subpackage Model 
  11. */ 
  12. class MS_Gateway_Stripe_Api extends MS_Model_Option { 
  13.  
  14. const ID = 'stripe'; 
  15.  
  16. /** 
  17. * Gateway singleton instance. 
  18. * 
  19. * @since 1.0.0 
  20. * @var string $instance 
  21. */ 
  22. public static $instance; 
  23.  
  24. /** 
  25. * Holds a reference to the parent gateway (either stripe or stripeplan) 
  26. * 
  27. * @since 1.0.1.0 
  28. * @var MS_Gateway_Stripe|MS_Gateway_Stripeplan 
  29. */ 
  30. protected $_gateway = null; 
  31.  
  32. /** 
  33. * Sets the parent gateway of the API object. 
  34. * 
  35. * The parent gateway object is used to fetch the API keys. 
  36. * 
  37. * @since 1.0.1.0 
  38. * @param MS_Gateway $gateway The parent gateway. 
  39. */ 
  40. public function set_gateway( $gateway ) { 
  41. static $Stripe_Loaded = false; 
  42.  
  43. if ( ! $Stripe_Loaded ) { 
  44. require_once MS_Plugin::instance()->dir . '/lib/stripe-php/lib/Stripe.php'; 
  45.  
  46. do_action( 
  47. 'ms_gateway_stripe_load_stripe_lib_after',  
  48. $this 
  49. ); 
  50.  
  51. $Stripe_Loaded = true; 
  52.  
  53. $this->_gateway = $gateway; 
  54.  
  55. $secret_key = $this->_gateway->get_secret_key(); 
  56. M2_Stripe::setApiKey( $secret_key ); 
  57.  
  58. /** 
  59. * Get Member's Stripe Customer Object, creates a new customer if not found. 
  60. * 
  61. * @since 1.0.0 
  62. * @internal 
  63. * 
  64. * @param MS_Model_Member $member The member. 
  65. * @param string $token The credit card token. 
  66. */ 
  67. public function get_stripe_customer( $member, $token ) { 
  68. $customer = $this->find_customer( $member ); 
  69.  
  70. if ( empty( $customer ) ) { 
  71. $customer = M2_Stripe_Customer::create( 
  72. array( 
  73. 'card' => $token,  
  74. 'email' => $member->email,  
  75. ); 
  76. $member->set_gateway_profile( self::ID, 'customer_id', $customer->id ); 
  77. $member->save(); 
  78. } else { 
  79. $this->add_card( $member, $customer, $token ); 
  80.  
  81. return apply_filters( 
  82. 'ms_gateway_stripe_get_stripe_customer',  
  83. $customer,  
  84. $member,  
  85. $this 
  86. ); 
  87.  
  88. /** 
  89. * Get Member's Stripe Customer Object. 
  90. * 
  91. * @since 1.0.0 
  92. * @internal 
  93. * 
  94. * @param MS_Model_Member $member The member. 
  95. */ 
  96. public function find_customer( $member ) { 
  97. $customer_id = $member->get_gateway_profile( self::ID, 'customer_id' ); 
  98. $customer = null; 
  99.  
  100. if ( ! empty( $customer_id ) ) { 
  101. $customer = M2_Stripe_Customer::retrieve( $customer_id ); 
  102.  
  103. // Seems like the customer was manually deleted on Stripe website. 
  104. if ( isset( $customer->deleted ) && $customer->deleted ) { 
  105. $customer = null; 
  106. $member->set_gateway_profile( self::ID, 'customer_id', '' ); 
  107.  
  108. return apply_filters( 
  109. 'ms_gateway_stripe_find_customer',  
  110. $customer,  
  111. $member,  
  112. $this 
  113. ); 
  114.  
  115. /** 
  116. * Add card info to Stripe customer profile and to WordPress user meta. 
  117. * 
  118. * @since 1.0.0 
  119. * @api 
  120. * 
  121. * @param MS_Model_Member $member The member model. 
  122. * @param M2_Stripe_Customer $customer The stripe customer object. 
  123. * @param string $token The stripe card token generated by the gateway. 
  124. */ 
  125. public function add_card( $member, $customer, $token ) { 
  126. $card = false; 
  127.  
  128. // 1. Save card to Stripe profile. 
  129.  
  130. // Stripe API until version 2015-02-16 
  131. if ( ! empty( $customer->cards ) ) { 
  132. $card = $customer->cards->create( array( 'card' => $token ) ); 
  133. $customer->default_card = $card->id; 
  134.  
  135. // Stripe API since 2015-02-18 
  136. if ( ! empty( $customer->sources ) ) { 
  137. $card = $customer->sources->create( array( 'card' => $token ) ); 
  138. $customer->default_source = $card->id; 
  139.  
  140. if ( $card ) { 
  141. $customer->save(); 
  142.  
  143. /** 
  144. * This action is used by the Taxamo Add-on to check additional country 
  145. * evidence (CC country). 
  146. * 
  147. * @since 1.0.0 
  148. */ 
  149. do_action( 'ms_gateway_stripe_credit_card_saved', $card, $member, $this ); 
  150.  
  151. // 2. Save card to WordPress user meta. 
  152.  
  153. if ( $card ) { 
  154. $member->set_gateway_profile( 
  155. self::ID,  
  156. 'card_exp',  
  157. gmdate( 'Y-m-d', strtotime( "{$card->exp_year}-{$card->exp_month}-01" ) ) 
  158. ); 
  159. $member->set_gateway_profile( self::ID, 'card_num', $card->last4 ); 
  160. $member->save(); 
  161.  
  162. do_action( 
  163. 'ms_gateway_stripe_add_card_info_after',  
  164. $customer,  
  165. $token,  
  166. $this 
  167. ); 
  168.  
  169. /** 
  170. * Creates a one-time charge that is immediately captured. 
  171. * 
  172. * This means the money is instantly transferred to our own stripe account. 
  173. * 
  174. * @since 1.0.0 
  175. * @internal 
  176. * 
  177. * @param M2_Stripe_Customer $customer Stripe customer to charge. 
  178. * @param float $amount Amount in currency (i.e. in USD, not in cents) 
  179. * @param string $currency 3-digit currency code. 
  180. * @param string $description This is displayed on the invoice to customer. 
  181. * @return M2_Stripe_Charge The resulting charge object. 
  182. */ 
  183. public function charge( $customer, $amount, $currency, $description ) { 
  184.  
  185. $amount = apply_filters( 
  186. 'ms_gateway_stripe_charge_amount',  
  187. $amount,  
  188. $currency 
  189. ); 
  190.  
  191. $charge = M2_Stripe_Charge::create( 
  192. array( 
  193. 'customer' => $customer->id,  
  194. 'amount' => intval( $amount * 100 ), // Amount in cents! 
  195. 'currency' => strtolower( $currency ),  
  196. 'description' => $description,  
  197. ); 
  198.  
  199. return apply_filters( 
  200. 'ms_gateway_stripe_charge',  
  201. $charge,  
  202. $customer,  
  203. $amount,  
  204. $currency,  
  205. $description,  
  206. $this 
  207. ); 
  208.  
  209. /** 
  210. * Fetches an existing subscription from Stripe and returns it. 
  211. * 
  212. * If the specified customer did not subscribe to the membership then 
  213. * boolean FALSE will be returned. 
  214. * 
  215. * @since 1.0.0 
  216. * @internal 
  217. * 
  218. * @param M2_Stripe_Customer $customer Stripe customer to charge. 
  219. * @param MS_Model_Membership $membership The membership. 
  220. * @return M2_Stripe_Subscription|false The resulting charge object. 
  221. */ 
  222. public function get_subscription( $customer, $membership ) { 
  223. $plan_id = MS_Gateway_Stripeplan::get_the_id( 
  224. $membership->id,  
  225. 'plan' 
  226. ); 
  227.  
  228. /** 
  229. * Check all subscriptions of the customer and find the subscription 
  230. * for the specified membership. 
  231. */ 
  232. $last_checked = false; 
  233. $has_more = false; 
  234. $subscription = false; 
  235.  
  236. do { 
  237. $args = array(); 
  238. if ( $last_checked ) { 
  239. $args['starting_after'] = $last_checked; 
  240. $active_subs = $customer->subscriptions->all( $args ); 
  241. $has_more = $active_subs->has_more; 
  242.  
  243. foreach ( $active_subs->data as $sub ) { 
  244. if ( $sub->plan->id == $plan_id ) { 
  245. $subscription = $sub; 
  246. $has_more = false; 
  247. break 2; 
  248. $last_checked = $sub->id; 
  249. } while ( $has_more ); 
  250.  
  251. return apply_filters( 
  252. 'ms_gateway_stripe_get_subscription',  
  253. $subscription,  
  254. $customer,  
  255. $membership,  
  256. $this 
  257. ); 
  258.  
  259. /** 
  260. * Creates a subscription that starts immediately. 
  261. * 
  262. * @since 1.0.0 
  263. * @internal 
  264. * 
  265. * @param M2_Stripe_Customer $customer Stripe customer to charge. 
  266. * @param MS_Model_Invoice $invoice The relevant invoice. 
  267. * @return M2_Stripe_Subscription The resulting charge object. 
  268. */ 
  269. public function subscribe( $customer, $invoice ) { 
  270. $membership = $invoice->get_membership(); 
  271. $plan_id = MS_Gateway_Stripeplan::get_the_id( 
  272. $membership->id,  
  273. 'plan' 
  274. ); 
  275.  
  276. $subscription = self::get_subscription( $customer, $membership ); 
  277.  
  278. /** 
  279. * If no active subscription was found for the membership create it. 
  280. */ 
  281. if ( ! $subscription ) { 
  282. $tax_percent = null; 
  283. $coupon_id = null; 
  284.  
  285. if ( is_numeric( $invoice->tax_rate ) && $invoice->tax_rate > 0 ) { 
  286. $tax_percent = floatval( $invoice->tax_rate ); 
  287. if ( $invoice->coupon_id ) { 
  288. $coupon_id = MS_Gateway_Stripeplan::get_the_id( 
  289. $invoice->coupon_id,  
  290. 'coupon' 
  291. ); 
  292.  
  293. $args = array( 
  294. 'plan' => $plan_id,  
  295. 'tax_percent' => $tax_percent,  
  296. 'coupon' => $coupon_id,  
  297. ); 
  298. $subscription = $customer->subscriptions->create( $args ); 
  299.  
  300. return apply_filters( 
  301. 'ms_gateway_stripe_subscribe',  
  302. $subscription,  
  303. $customer,  
  304. $invoice,  
  305. $membership,  
  306. $this 
  307. ); 
  308.  
  309. /** 
  310. * Creates or updates the payment plan specified by the function parameter. 
  311. * 
  312. * @since 1.0.0 
  313. * @internal 
  314. * 
  315. * @param array $plan_data The plan-object containing all details for Stripe. 
  316. */ 
  317. public function create_or_update_plan( $plan_data ) { 
  318. $item_id = $plan_data['id']; 
  319. $all_items = MS_Factory::get_transient( 'ms_stripeplan_plans' ); 
  320. $all_items = lib3()->array->get( $all_items ); 
  321.  
  322. if ( ! isset( $all_items[$item_id] ) 
  323. || ! is_a( $all_items[$item_id], 'M2_Stripe_Plan' ) 
  324. ) { 
  325. try { 
  326. $item = M2_Stripe_Plan::retrieve( $item_id ); 
  327. } catch( Exception $e ) { 
  328. // If the plan does not exist then stripe will throw an Exception. 
  329. $item = false; 
  330. $all_items[$item_id] = $item; 
  331. } else { 
  332. $item = $all_items[$item_id]; 
  333.  
  334. /** 
  335. * Stripe can only update the plan-name, so we have to delete and 
  336. * recreate the plan manually. 
  337. */ 
  338. if ( $item && is_a( $item, 'M2_Stripe_Plan' ) ) { 
  339. $item->delete(); 
  340. $all_items[$item_id] = false; 
  341.  
  342. if ( $plan_data['amount'] > 0 ) { 
  343. $item = M2_Stripe_Plan::create( $plan_data ); 
  344. $all_items[$item_id] = $item; 
  345.  
  346. MS_Factory::set_transient( 
  347. 'ms_stripeplan_plans',  
  348. $all_items,  
  349. HOUR_IN_SECONDS 
  350. ); 
  351.  
  352. /** 
  353. * Creates or updates the coupon specified by the function parameter. 
  354. * 
  355. * @since 1.0.0 
  356. * @internal 
  357. * 
  358. * @param array $coupon_data The object containing all details for Stripe. 
  359. */ 
  360. public function create_or_update_coupon( $coupon_data ) { 
  361. $item_id = $coupon_data['id']; 
  362. $all_items = MS_Factory::get_transient( 'ms_stripeplan_plans' ); 
  363. $all_items = lib3()->array->get( $all_items ); 
  364.  
  365. if ( ! isset( $all_items[$item_id] ) 
  366. || ! is_a( $all_items[$item_id], 'M2_Stripe_Coupon' ) 
  367. ) { 
  368. try { 
  369. $item = M2_Stripe_Coupon::retrieve( $item_id ); 
  370. } catch( Exception $e ) { 
  371. // If the coupon does not exist then stripe will throw an Exception. 
  372. $item = false; 
  373. $all_items[$item_id] = $item; 
  374. } else { 
  375. $item = $all_items[$item_id]; 
  376.  
  377. /** 
  378. * Stripe can only update the coupon-name, so we have to delete and 
  379. * recreate the coupon manually. 
  380. */ 
  381. if ( $item && is_a( $item, 'M2_Stripe_Coupon' ) ) { 
  382. $item->delete(); 
  383. $all_items[$item_id] = false; 
  384.  
  385. $item = M2_Stripe_Coupon::create( $coupon_data ); 
  386. $all_items[$item_id] = $item; 
  387.  
  388. MS_Factory::set_transient( 
  389. 'ms_stripeplan_coupons',  
  390. $all_items,  
  391. HOUR_IN_SECONDS 
  392. ); 
  393.  
  394. /** 
  395. * Little hack to force the plugin to store/load the stripe_api data in same 
  396. * option-field as the stripe-gateway settings. 
  397. * 
  398. * @since 1.0.0 
  399. * @return string 
  400. */ 
  401. public function option_key() { 
  402. // Option key should be all lowercase. 
  403. $key = 'ms_gateway_stripe'; 
  404.  
  405. // Network-wide mode uses different options then single-site mode. 
  406. if ( MS_Plugin::is_network_wide() ) { 
  407. $key .= '-network'; 
  408.  
  409. return $key; 
.