PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/woocommerce-gateway-paypal-powered-by-braintree/classes/class-wc-gateway-paypal-braintree-subscription.php

https://gitlab.com/hunt9310/ras
PHP | 355 lines | 232 code | 55 blank | 68 comment | 30 complexity | 6e8645a36b58255c55005262d15b64c0 MD5 | raw file
  1. <?php
  2. /**
  3. * WC_Gateway_Paypal_Braintree_Subscription class.
  4. *
  5. * @extends WC_Gateway_Paypal_Braintree
  6. */
  7. abstract class WC_Gateway_Paypal_Braintree_Subscription extends WC_Gateway_Paypal_Braintree {
  8. /**
  9. * Constructor
  10. */
  11. public function __construct() {
  12. parent::__construct();
  13. // Add subscription support
  14. $this->supports = array_merge( $this->supports, array(
  15. 'subscriptions',
  16. 'subscription_cancellation',
  17. 'subscription_suspension',
  18. 'subscription_reactivation',
  19. 'subscription_amount_changes',
  20. 'subscription_date_changes',
  21. 'multiple_subscriptions',
  22. 'subscription_payment_method_change_admin',
  23. 'subscription_payment_method_change_customer',
  24. )
  25. );
  26. // process renewals
  27. add_action( 'woocommerce_scheduled_subscription_payment_' . $this->id, array( $this, 'scheduled_subscription_payment' ), 10, 2 );
  28. // filter javascript options
  29. add_filter( 'wc_gateway_paypal_braintree_data', array( $this, 'filter_paypal_braintree_data' ), 10 );
  30. }
  31. /**
  32. * Check if order contains subscriptions.
  33. *
  34. * @param int $order_id
  35. * @return bool
  36. */
  37. protected function order_contains_subscription( $order_id ) {
  38. return function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) );
  39. }
  40. /**
  41. * process_payment
  42. *
  43. * Completes the initial payment on the subscription order
  44. *
  45. * Although PayPal Braintree supports subscription products (they call them plans),
  46. * they don't support the wide variety of intervals we do, nor multiple subscription
  47. * products in a single order.
  48. *
  49. * So, this extension does all the subscription work itself, storing a customer
  50. * in the vault on the first payment, and using those stored credentials for
  51. * renewal orders
  52. *
  53. * @param int $order_id
  54. * @return mixed
  55. */
  56. public function process_payment( $order_id ) {
  57. // If the order contains no subscriptions, just let the parent process it
  58. if ( ! $this->order_contains_subscription( $order_id ) ) {
  59. return parent::process_payment( $order_id );
  60. }
  61. $order = new WC_Order( $order_id );
  62. $this->log( __FUNCTION__, "Info: Beginning processing of payment for (subscription) order $order_id for the amount of {$order->order_total}" );
  63. $this->log( __FUNCTION__, "Info: Merchant ID = {$this->merchant_id}" );
  64. $paypal_braintree_nonce = self::get_posted_variable( 'paypalbraintree_nonce' );
  65. if ( empty( $paypal_braintree_nonce ) ) {
  66. $this->log( __FUNCTION__, 'Error: The paypal_braintree_nonce was unexpectedly empty' );
  67. wc_add_notice( __( 'Error: PayPal Powered by Braintree did not supply a payment nonce. Please try again later or use another means of payment.', 'woocommerce-gateway-paypal-braintree' ), 'error' );
  68. return false;
  69. }
  70. $user_id = $order->get_user_id();
  71. if ( ! is_user_logged_in() ) {
  72. $this->log( __FUNCTION__, 'Error: No user logged in / being created for the initial subscription payment' );
  73. wc_add_notice( __( 'Error: You must login or create an account before you can purchase a subscription.', 'woocommerce-gateway-paypal-braintree' ), 'error' );
  74. return false;
  75. }
  76. // Create the gateway instance up front
  77. require_once( dirname( __FILE__ ) . '/../braintree_sdk/lib/Braintree.php' );
  78. $gateway = new Braintree_Gateway( array(
  79. 'accessToken' => $this->merchant_access_token,
  80. ) );
  81. // Check their user meta for a stored braintree customer id
  82. $braintree_customer_id = get_user_meta( $user_id, '_wc_paypal_braintree_customer_id', true );
  83. if ( empty( $braintree_customer_id ) ) {
  84. $this->log( __FUNCTION__, 'Info: Did not find braintree customer id on user meta. Need to create customer' );
  85. // Create a new customer id, passing the nonce so we can add the card to the vault
  86. // ref https://developers.braintreepayments.com/reference/request/customer/create/php
  87. $customer_args = array(
  88. 'firstName' => $order->billing_first_name,
  89. 'lastName' => $order->billing_last_name,
  90. 'company' => $order->billing_company,
  91. 'phone' => $order->billing_phone,
  92. 'email' => $order->billing_email,
  93. 'paymentMethodNonce' => $paypal_braintree_nonce,
  94. );
  95. try {
  96. $result = $gateway->customer()->create( $customer_args );
  97. } catch ( Exception $e ) {
  98. $this->log( __FUNCTION__, 'Error: Unable to create customer. Reason: ' . $e->getMessage() );
  99. wc_add_notice( __( 'Error: PayPal Powered by Braintree was unable to create a customer record for you. Please try again later or use another means of payment.', 'woocommerce-gateway-paypal-braintree' ), 'error' );
  100. return false;
  101. }
  102. if ( ! $result->success ) {
  103. $this->log( __FUNCTION__, "Error: Unable to create customer: {$result->message}" );
  104. wc_add_notice( __( 'Error: PayPal Powered by Braintree was unable to create a customer record for you. Please try again later or use another means of payment.', 'woocommerce-gateway-paypal-braintree' ), 'error' );
  105. return false;
  106. }
  107. $braintree_customer_id = $result->customer->id;
  108. update_user_meta( $user_id, '_wc_paypal_braintree_customer_id', $braintree_customer_id );
  109. $this->log( __FUNCTION__, "Info: Created customer successfully - braintree customer id = $braintree_customer_id" );
  110. $payment_methods = $result->customer->paymentMethods;
  111. $payment_method_token = '';
  112. foreach ( (array) $payment_methods as $payment_method ) {
  113. if ( $payment_method->default ) {
  114. $payment_method_token = $payment_method->token;
  115. }
  116. }
  117. $authentication = array(
  118. 'paymentMethodToken' => $payment_method_token, // can only use the nonce once :)
  119. );
  120. } else {
  121. // We found the braintree customer id in the customer's meta
  122. $this->log( __FUNCTION__, "Info: Found a braintree customer id in the users meta - customer id = $braintree_customer_id" );
  123. $authentication = array(
  124. 'paymentMethodNonce' => $paypal_braintree_nonce,
  125. );
  126. }
  127. $sale_args = $this->generate_sales_args( $order, $braintree_customer_id );
  128. $sale_args = array_merge( $sale_args, $authentication );
  129. $transaction_id = '';
  130. // Process trial periods and possible coupon discounts.
  131. if ( isset( $sale_args['amount'] ) && 0.00 === doubleval( $sale_args['amount'] ) ) {
  132. $user_id = $order->get_user_id();
  133. $this->log( __FUNCTION__, "Zero payment amount for trial or coupon. Order ID: $order_id, User ID: $user_id" );
  134. $customer = $gateway->customer()->find( $braintree_customer_id );
  135. $payment_method_token = '';
  136. foreach ( (array) $customer->paymentMethods as $payment_method ) {
  137. if ( $payment_method->default ) {
  138. $payment_method_token = $payment_method->token;
  139. }
  140. }
  141. } else { // charges more than zero should be sent away
  142. // We have a customer id now, so let's do the sale and store the payment method in the vault.
  143. $result = $gateway->transaction()->sale( $sale_args );
  144. if ( ! $result->success ) {
  145. $notice = sprintf( __( 'Error: PayPal Powered by Braintree was unable to complete the transaction. Please try again later or use another means of payment. Reason: %s', 'woocommerce-gateway-paypal-braintree' ), $error_message );
  146. wc_add_notice( $notice, 'error' );
  147. $this->log( __FUNCTION__, "Error: Unable to complete transaction. Reason: {$result->message}" );
  148. return false;
  149. }
  150. $transaction_id = $result->transaction->id;
  151. $this->log( __FUNCTION__, "Info: Successfully processed initial payment, transaction id = $transaction_id" );
  152. $credit_card_meta = $result->transaction->creditCard;
  153. $payment_method_token = $credit_card_meta['token'];
  154. if ( empty( $payment_method_token ) ) {
  155. $this->log( __FUNCTION__, 'Info: Customer used the paypal subflow' );
  156. $paypal_meta = $result->transaction->paypal;
  157. $payment_method_token = $paypal_meta['token'];
  158. } else {
  159. $this->log( __FUNCTION__, 'Info: Customer used the credit card subflow' );
  160. }
  161. $braintree_customer_id = $result->transaction->customer['id'];
  162. }
  163. if ( empty( $payment_method_token ) ) {
  164. $this->log( __FUNCTION__, 'Warning: Initial payment succeeded, but no token was provided by the gateway for recurring payments.' );
  165. }
  166. // Save the customer ID in each subscription for this order for later use during renewal
  167. if ( empty( $braintree_customer_id ) ) {
  168. $this->log( __FUNCTION__, 'Warning: Initial payment succeeded, but no braintree customer ID was provided by the gateway for recurring payments.' );
  169. } else {
  170. $this->log( __FUNCTION__, "Info: Saving to subscription(s) recurring payment braintree customer ID $braintree_customer_id" );
  171. }
  172. // Note: A single order may contain multiple subscriptions
  173. // Save the token in each subscription for this order for later use during renewal
  174. foreach ( wcs_get_subscriptions_for_order( $order->id ) as $subscription ) {
  175. update_post_meta( $subscription->id, '_wc_paypal_braintree_payment_method_token', $payment_method_token );
  176. update_post_meta( $subscription->id, '_wc_paypal_braintree_customer_id', $braintree_customer_id );
  177. }
  178. $order->payment_complete( $transaction_id );
  179. $this->log( __FUNCTION__, "Info: Completed processing of payment for order $order_id" );
  180. return array(
  181. 'result' => 'success',
  182. 'redirect' => $this->get_return_url( $order ),
  183. );
  184. }
  185. /**
  186. * Create sales args.
  187. * @param $order
  188. * @param $braintree_customer_id
  189. *
  190. * @return array
  191. */
  192. public function generate_sales_args( $order, $braintree_customer_id ) {
  193. $billing = array(
  194. 'firstName' => $order->billing_first_name,
  195. 'lastName' => $order->billing_last_name,
  196. 'company' => $order->billing_company,
  197. 'streetAddress' => $order->billing_address_1,
  198. 'extendedAddress' => $order->billing_address_2,
  199. 'locality' => $order->billing_city,
  200. 'region' => $order->billing_state,
  201. 'postalCode' => $order->billing_postcode,
  202. 'countryCodeAlpha2' => $order->billing_country,
  203. );
  204. // Shipping data, assemble
  205. $shipping = array(
  206. 'firstName' => $order->shipping_first_name,
  207. 'lastName' => $order->shipping_last_name,
  208. 'company' => $order->shipping_company,
  209. 'streetAddress' => $order->shipping_address_1,
  210. 'extendedAddress' => $order->shipping_address_2,
  211. 'locality' => $order->shipping_city,
  212. 'region' => $order->shipping_state,
  213. 'postalCode' => $order->shipping_postcode,
  214. 'countryCodeAlpha2' => $order->shipping_country,
  215. );
  216. $sale_args = array(
  217. 'amount' => $order->order_total,
  218. 'billing' => $billing,
  219. 'shipping' => $shipping,
  220. 'customerId' => $braintree_customer_id,
  221. 'channel' => 'WooThemes_BT', // aka BN tracking code
  222. 'orderId' => $order->id,
  223. 'options' => array(
  224. 'submitForSettlement' => true,
  225. 'storeInVaultOnSuccess' => true,
  226. ),
  227. );
  228. return $sale_args;
  229. }
  230. /**
  231. * scheduled_subscription_payment
  232. *
  233. * Hooked to woocommerce_scheduled_subscription_payment_{gateway_id}
  234. * Completes recurring payments for a subscription
  235. */
  236. public function scheduled_subscription_payment( $amount_to_charge, $order ) {
  237. $this->log( __FUNCTION__, "Info: Beginning processing of scheduled payment for order {$order->id} for the amount of $amount_to_charge" );
  238. $this->log( __FUNCTION__, "Info: Merchant ID = {$this->merchant_id}" );
  239. // token is required
  240. $payment_method_token = get_post_meta( $order->id, '_wc_paypal_braintree_payment_method_token', true );
  241. if ( empty( $payment_method_token ) ) {
  242. $this->log( __FUNCTION__, "Error: Payment method token is missing on order meta" );
  243. WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $order );
  244. return;
  245. }
  246. // as is the customer id
  247. $braintree_customer_id = get_post_meta( $order->id, '_wc_paypal_braintree_customer_id', true );
  248. if ( empty( $braintree_customer_id ) ) {
  249. $this->log( __FUNCTION__, "Error: Braintree customer ID is missing on order meta" );
  250. WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $order );
  251. return;
  252. }
  253. // Create the gateway instance
  254. require_once( dirname( __FILE__ ) . '/../braintree_sdk/lib/Braintree.php' );
  255. $gateway = new Braintree_Gateway( array(
  256. 'accessToken' => $this->merchant_access_token,
  257. ) );
  258. // Process the sale with the stored token and customer
  259. $sale_args = array(
  260. 'amount' => $amount_to_charge,
  261. 'paymentMethodToken' => $payment_method_token,
  262. 'recurring' => true,
  263. 'customerId' => $braintree_customer_id,
  264. 'channel' => 'WooThemes_BT', // aka BN tracking code
  265. 'orderId' => $order->id,
  266. 'options' => array(
  267. 'submitForSettlement' => true,
  268. 'storeInVaultOnSuccess' => true
  269. )
  270. );
  271. try {
  272. $result = $gateway->transaction()->sale( $sale_args );
  273. } catch ( Exception $e ) {
  274. $this->log( __FUNCTION__, 'Error: Unable to process scheduled payment. Reason: ' . $e->getMessage() );
  275. return false;
  276. }
  277. if ( ! $result->success ) {
  278. $this->log( __FUNCTION__, "Error: Unable to process scheduled payment: {$result->message}" );
  279. return false;
  280. }
  281. $transaction_id = $result->transaction->id;
  282. $this->log( __FUNCTION__, "Info: Successfully processed schedule payment, transaction id = $transaction_id" );
  283. WC_Subscriptions_Manager::process_subscription_payments_on_order( $order );
  284. $this->log( __FUNCTION__, "Info: Completed processing of scheduled payment for order {$order->id}" );
  285. $order->add_order_note( sprintf( __( 'PayPal Braintree charge complete (Charge ID: %s)', 'woocommerce-gateway-paypal-braintree' ), $transaction_id ) );
  286. $order->payment_complete( $transaction_id );
  287. }
  288. /**
  289. * filter_paypal_braintree_data
  290. *
  291. * For subscriptions, set the singleUse flag to false so paypal-braintree knows we need a token
  292. * for recurring payments
  293. */
  294. public function filter_paypal_braintree_data( $paypal_braintree_data ) {
  295. if ( array_key_exists( 'checkoutWithPayPal' , $paypal_braintree_data ) ) {
  296. $paypal_braintree_data['checkoutWithPayPal']['singleUse'] = false;
  297. }
  298. return $paypal_braintree_data;
  299. }
  300. }