class-wc-addons-gateway-simplify-commerce.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. <?php
  2. if ( ! defined( 'ABSPATH' ) ) {
  3. exit; // Exit if accessed directly
  4. }
  5. /**
  6. * Simplify Commerce Gateway for subscriptions.
  7. *
  8. * @class WC_Addons_Gateway_Simplify_Commerce
  9. * @extends WC_Gateway_Simplify_Commerce
  10. * @since 2.2.0
  11. * @version 1.0.0
  12. * @package WooCommerce/Classes/Payment
  13. * @author WooThemes
  14. */
  15. class WC_Addons_Gateway_Simplify_Commerce extends WC_Gateway_Simplify_Commerce {
  16. /**
  17. * Constructor.
  18. */
  19. public function __construct() {
  20. parent::__construct();
  21. if ( class_exists( 'WC_Subscriptions_Order' ) ) {
  22. add_action( 'woocommerce_scheduled_subscription_payment_' . $this->id, array( $this, 'scheduled_subscription_payment' ), 10, 2 );
  23. add_action( 'woocommerce_subscription_failing_payment_method_updated_' . $this->id, array( $this, 'update_failing_payment_method' ), 10, 2 );
  24. add_action( 'wcs_resubscribe_order_created', array( $this, 'delete_resubscribe_meta' ), 10 );
  25. // Allow store managers to manually set Simplify as the payment method on a subscription
  26. add_filter( 'woocommerce_subscription_payment_meta', array( $this, 'add_subscription_payment_meta' ), 10, 2 );
  27. add_filter( 'woocommerce_subscription_validate_payment_meta', array( $this, 'validate_subscription_payment_meta' ), 10, 2 );
  28. }
  29. if ( class_exists( 'WC_Pre_Orders_Order' ) ) {
  30. add_action( 'wc_pre_orders_process_pre_order_completion_payment_' . $this->id, array( $this, 'process_pre_order_release_payment' ) );
  31. }
  32. add_filter( 'woocommerce_simplify_commerce_hosted_args', array( $this, 'hosted_payment_args' ), 10, 2 );
  33. add_action( 'woocommerce_api_wc_addons_gateway_simplify_commerce', array( $this, 'return_handler' ) );
  34. }
  35. /**
  36. * Hosted payment args.
  37. *
  38. * @param array $args
  39. * @param int $order_id
  40. * @return array
  41. */
  42. public function hosted_payment_args( $args, $order_id ) {
  43. if ( ( $this->order_contains_subscription( $order_id ) ) || ( $this->order_contains_pre_order( $order_id ) && WC_Pre_Orders_Order::order_requires_payment_tokenization( $order_id ) ) ) {
  44. $args['operation'] = 'create.token';
  45. }
  46. $args['redirect-url'] = WC()->api_request_url( 'WC_Addons_Gateway_Simplify_Commerce' );
  47. return $args;
  48. }
  49. /**
  50. * Check if order contains subscriptions.
  51. *
  52. * @param int $order_id
  53. * @return bool
  54. */
  55. protected function order_contains_subscription( $order_id ) {
  56. return function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) );
  57. }
  58. /**
  59. * Check if order contains pre-orders.
  60. *
  61. * @param int $order_id
  62. * @return bool
  63. */
  64. protected function order_contains_pre_order( $order_id ) {
  65. return class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Order::order_contains_pre_order( $order_id );
  66. }
  67. /**
  68. * Process the subscription.
  69. *
  70. * @param WC_Order $order
  71. * @param string $cart_token
  72. * @uses Simplify_ApiException
  73. * @uses Simplify_BadRequestException
  74. * @return array
  75. * @throws Exception
  76. */
  77. protected function process_subscription( $order, $cart_token = '' ) {
  78. try {
  79. if ( empty( $cart_token ) ) {
  80. $error_msg = __( 'Please make sure your card details have been entered correctly and that your browser supports JavaScript.', 'woocommerce' );
  81. if ( 'yes' == $this->sandbox ) {
  82. $error_msg .= ' ' . __( 'Developers: Please make sure that you are including jQuery and there are no JavaScript errors on the page.', 'woocommerce' );
  83. }
  84. throw new Simplify_ApiException( $error_msg );
  85. }
  86. // Create customer
  87. $customer = Simplify_Customer::createCustomer(
  88. array(
  89. 'token' => $cart_token,
  90. 'email' => $order->get_billing_email(),
  91. 'name' => trim( $order->get_formatted_billing_full_name() ),
  92. 'reference' => $order->get_id(),
  93. )
  94. );
  95. if ( is_object( $customer ) && '' != $customer->id ) {
  96. $this->save_subscription_meta( $order->get_id(), $customer->id );
  97. } else {
  98. $error_msg = __( 'Error creating user in Simplify Commerce.', 'woocommerce' );
  99. throw new Simplify_ApiException( $error_msg );
  100. }
  101. $payment_response = $this->process_subscription_payment( $order, $order->get_total() );
  102. if ( is_wp_error( $payment_response ) ) {
  103. throw new Exception( $payment_response->get_error_message() );
  104. } else {
  105. // Remove cart
  106. WC()->cart->empty_cart();
  107. // Return thank you page redirect
  108. return array(
  109. 'result' => 'success',
  110. 'redirect' => $this->get_return_url( $order ),
  111. );
  112. }
  113. } catch ( Simplify_ApiException $e ) {
  114. if ( $e instanceof Simplify_BadRequestException && $e->hasFieldErrors() && $e->getFieldErrors() ) {
  115. foreach ( $e->getFieldErrors() as $error ) {
  116. wc_add_notice( $error->getFieldName() . ': "' . $error->getMessage() . '" (' . $error->getErrorCode() . ')', 'error' );
  117. }
  118. } else {
  119. wc_add_notice( $e->getMessage(), 'error' );
  120. }
  121. return array(
  122. 'result' => 'fail',
  123. 'redirect' => '',
  124. );
  125. }
  126. }
  127. /**
  128. * Store the customer and card IDs on the order and subscriptions in the order.
  129. *
  130. * @param int $order_id
  131. * @param string $customer_id
  132. */
  133. protected function save_subscription_meta( $order_id, $customer_id ) {
  134. $customer_id = wc_clean( $customer_id );
  135. update_post_meta( $order_id, '_simplify_customer_id', $customer_id );
  136. // Also store it on the subscriptions being purchased in the order
  137. foreach ( wcs_get_subscriptions_for_order( $order_id ) as $subscription ) {
  138. update_post_meta( $subscription->id, '_simplify_customer_id', $customer_id );
  139. }
  140. }
  141. /**
  142. * Process the pre-order.
  143. *
  144. * @param WC_Order $order
  145. * @param string $cart_token
  146. * @uses Simplify_ApiException
  147. * @uses Simplify_BadRequestException
  148. * @return array
  149. */
  150. protected function process_pre_order( $order, $cart_token = '' ) {
  151. if ( WC_Pre_Orders_Order::order_requires_payment_tokenization( $order->get_id() ) ) {
  152. try {
  153. if ( $order->get_total() * 100 < 50 ) {
  154. $error_msg = __( 'Sorry, the minimum allowed order total is 0.50 to use this payment method.', 'woocommerce' );
  155. throw new Simplify_ApiException( $error_msg );
  156. }
  157. if ( empty( $cart_token ) ) {
  158. $error_msg = __( 'Please make sure your card details have been entered correctly and that your browser supports JavaScript.', 'woocommerce' );
  159. if ( 'yes' == $this->sandbox ) {
  160. $error_msg .= ' ' . __( 'Developers: Please make sure that you are including jQuery and there are no JavaScript errors on the page.', 'woocommerce' );
  161. }
  162. throw new Simplify_ApiException( $error_msg );
  163. }
  164. // Create customer
  165. $customer = Simplify_Customer::createCustomer(
  166. array(
  167. 'token' => $cart_token,
  168. 'email' => $order->get_billing_email(),
  169. 'name' => trim( $order->get_formatted_billing_full_name() ),
  170. 'reference' => $order->get_id(),
  171. )
  172. );
  173. if ( is_object( $customer ) && '' != $customer->id ) {
  174. $customer_id = wc_clean( $customer->id );
  175. // Store the customer ID in the order
  176. update_post_meta( $order->get_id(), '_simplify_customer_id', $customer_id );
  177. } else {
  178. $error_msg = __( 'Error creating user in Simplify Commerce.', 'woocommerce' );
  179. throw new Simplify_ApiException( $error_msg );
  180. }
  181. // Reduce stock levels
  182. wc_reduce_stock_levels( $order->get_id() );
  183. // Remove cart
  184. WC()->cart->empty_cart();
  185. // Is pre ordered!
  186. WC_Pre_Orders_Order::mark_order_as_pre_ordered( $order );
  187. // Return thank you page redirect
  188. return array(
  189. 'result' => 'success',
  190. 'redirect' => $this->get_return_url( $order ),
  191. );
  192. } catch ( Simplify_ApiException $e ) {
  193. if ( $e instanceof Simplify_BadRequestException && $e->hasFieldErrors() && $e->getFieldErrors() ) {
  194. foreach ( $e->getFieldErrors() as $error ) {
  195. wc_add_notice( $error->getFieldName() . ': "' . $error->getMessage() . '" (' . $error->getErrorCode() . ')', 'error' );
  196. }
  197. } else {
  198. wc_add_notice( $e->getMessage(), 'error' );
  199. }
  200. return array(
  201. 'result' => 'fail',
  202. 'redirect' => '',
  203. );
  204. }
  205. } else {
  206. return parent::process_standard_payments( $order, $cart_token );
  207. }
  208. }
  209. /**
  210. * Process the payment.
  211. *
  212. * @param int $order_id
  213. * @return array
  214. */
  215. public function process_payment( $order_id ) {
  216. $cart_token = isset( $_POST['simplify_token'] ) ? wc_clean( $_POST['simplify_token'] ) : '';
  217. $order = wc_get_order( $order_id );
  218. // Processing subscription
  219. if ( 'standard' == $this->mode && ( $this->order_contains_subscription( $order->get_id() ) || ( function_exists( 'wcs_is_subscription' ) && wcs_is_subscription( $order_id ) ) ) ) {
  220. return $this->process_subscription( $order, $cart_token );
  221. } elseif ( 'standard' == $this->mode && $this->order_contains_pre_order( $order->get_id() ) ) {
  222. // Processing pre-order.
  223. return $this->process_pre_order( $order, $cart_token );
  224. } else {
  225. // Processing regular product.
  226. return parent::process_payment( $order_id );
  227. }
  228. }
  229. /**
  230. * process_subscription_payment function.
  231. *
  232. * @param WC_order $order
  233. * @param int $amount (default: 0)
  234. * @uses Simplify_BadRequestException
  235. * @return bool|WP_Error
  236. */
  237. public function process_subscription_payment( $order, $amount = 0 ) {
  238. if ( 0 == $amount ) {
  239. // Payment complete
  240. $order->payment_complete();
  241. return true;
  242. }
  243. if ( $amount * 100 < 50 ) {
  244. return new WP_Error( 'simplify_error', __( 'Sorry, the minimum allowed order total is 0.50 to use this payment method.', 'woocommerce' ) );
  245. }
  246. $customer_id = get_post_meta( $order->get_id(), '_simplify_customer_id', true );
  247. if ( ! $customer_id ) {
  248. return new WP_Error( 'simplify_error', __( 'Customer not found.', 'woocommerce' ) );
  249. }
  250. try {
  251. // Charge the customer
  252. $payment = Simplify_Payment::createPayment(
  253. array(
  254. 'amount' => $amount * 100, // In cents.
  255. 'customer' => $customer_id,
  256. 'description' => sprintf( __( '%1$s - Order #%2$s', 'woocommerce' ), esc_html( get_bloginfo( 'name', 'display' ) ), $order->get_order_number() ),
  257. 'currency' => strtoupper( get_woocommerce_currency() ),
  258. 'reference' => $order->get_id(),
  259. )
  260. );
  261. } catch ( Exception $e ) {
  262. $error_message = $e->getMessage();
  263. if ( $e instanceof Simplify_BadRequestException && $e->hasFieldErrors() && $e->getFieldErrors() ) {
  264. $error_message = '';
  265. foreach ( $e->getFieldErrors() as $error ) {
  266. $error_message .= ' ' . $error->getFieldName() . ': "' . $error->getMessage() . '" (' . $error->getErrorCode() . ')';
  267. }
  268. }
  269. $order->add_order_note( sprintf( __( 'Simplify payment error: %s.', 'woocommerce' ), $error_message ) );
  270. return new WP_Error( 'simplify_payment_declined', $e->getMessage(), array( 'status' => $e->getCode() ) );
  271. }
  272. if ( 'APPROVED' == $payment->paymentStatus ) {
  273. // Payment complete
  274. $order->payment_complete( $payment->id );
  275. // Add order note
  276. $order->add_order_note( sprintf( __( 'Simplify payment approved (ID: %1$s, Auth Code: %2$s)', 'woocommerce' ), $payment->id, $payment->authCode ) );
  277. return true;
  278. } else {
  279. $order->add_order_note( __( 'Simplify payment declined', 'woocommerce' ) );
  280. return new WP_Error( 'simplify_payment_declined', __( 'Payment was declined - please try another card.', 'woocommerce' ) );
  281. }
  282. }
  283. /**
  284. * scheduled_subscription_payment function.
  285. *
  286. * @param float $amount_to_charge The amount to charge.
  287. * @param WC_Order $renewal_order A WC_Order object created to record the renewal payment.
  288. */
  289. public function scheduled_subscription_payment( $amount_to_charge, $renewal_order ) {
  290. $result = $this->process_subscription_payment( $renewal_order, $amount_to_charge );
  291. if ( is_wp_error( $result ) ) {
  292. $renewal_order->update_status( 'failed', sprintf( __( 'Simplify Transaction Failed (%s)', 'woocommerce' ), $result->get_error_message() ) );
  293. }
  294. }
  295. /**
  296. * Update the customer_id for a subscription after using Simplify to complete a payment to make up for.
  297. * an automatic renewal payment which previously failed.
  298. *
  299. * @param WC_Subscription $subscription The subscription for which the failing payment method relates.
  300. * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment).
  301. */
  302. public function update_failing_payment_method( $subscription, $renewal_order ) {
  303. update_post_meta( $subscription->id, '_simplify_customer_id', get_post_meta( $renewal_order->get_id(), '_simplify_customer_id', true ) );
  304. }
  305. /**
  306. * Include the payment meta data required to process automatic recurring payments so that store managers can.
  307. * manually set up automatic recurring payments for a customer via the Edit Subscription screen in Subscriptions v2.0+.
  308. *
  309. * @since 2.4
  310. * @param array $payment_meta associative array of meta data required for automatic payments
  311. * @param WC_Subscription $subscription An instance of a subscription object
  312. * @return array
  313. */
  314. public function add_subscription_payment_meta( $payment_meta, $subscription ) {
  315. $payment_meta[ $this->id ] = array(
  316. 'post_meta' => array(
  317. '_simplify_customer_id' => array(
  318. 'value' => get_post_meta( $subscription->id, '_simplify_customer_id', true ),
  319. 'label' => 'Simplify Customer ID',
  320. ),
  321. ),
  322. );
  323. return $payment_meta;
  324. }
  325. /**
  326. * Validate the payment meta data required to process automatic recurring payments so that store managers can.
  327. * manually set up automatic recurring payments for a customer via the Edit Subscription screen in Subscriptions 2.0+.
  328. *
  329. * @since 2.4
  330. * @param string $payment_method_id The ID of the payment method to validate
  331. * @param array $payment_meta associative array of meta data required for automatic payments
  332. * @throws Exception
  333. */
  334. public function validate_subscription_payment_meta( $payment_method_id, $payment_meta ) {
  335. if ( $this->id === $payment_method_id ) {
  336. if ( ! isset( $payment_meta['post_meta']['_simplify_customer_id']['value'] ) || empty( $payment_meta['post_meta']['_simplify_customer_id']['value'] ) ) {
  337. throw new Exception( 'A "_simplify_customer_id" value is required.' );
  338. }
  339. }
  340. }
  341. /**
  342. * Don't transfer customer meta to resubscribe orders.
  343. *
  344. * @access public
  345. * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
  346. * @return void
  347. */
  348. public function delete_resubscribe_meta( $resubscribe_order ) {
  349. delete_post_meta( $resubscribe_order->get_id(), '_simplify_customer_id' );
  350. }
  351. /**
  352. * Process a pre-order payment when the pre-order is released.
  353. *
  354. * @param WC_Order $order
  355. * @return WP_Error|null
  356. */
  357. public function process_pre_order_release_payment( $order ) {
  358. try {
  359. $order_items = $order->get_items();
  360. $order_item = array_shift( $order_items );
  361. /* translators: 1: site name 2: product name 3: order number */
  362. $pre_order_name = sprintf(
  363. __( '%1$s - Pre-order for "%2$s" (Order #%3$s)', 'woocommerce' ),
  364. esc_html( get_bloginfo( 'name', 'display' ) ),
  365. $order_item['name'],
  366. $order->get_order_number()
  367. );
  368. $customer_id = get_post_meta( $order->get_id(), '_simplify_customer_id', true );
  369. if ( ! $customer_id ) {
  370. return new WP_Error( 'simplify_error', __( 'Customer not found.', 'woocommerce' ) );
  371. }
  372. // Charge the customer
  373. $payment = Simplify_Payment::createPayment(
  374. array(
  375. 'amount' => $order->get_total() * 100, // In cents.
  376. 'customer' => $customer_id,
  377. 'description' => trim( substr( $pre_order_name, 0, 1024 ) ),
  378. 'currency' => strtoupper( get_woocommerce_currency() ),
  379. 'reference' => $order->get_id(),
  380. )
  381. );
  382. if ( 'APPROVED' == $payment->paymentStatus ) {
  383. // Payment complete
  384. $order->payment_complete( $payment->id );
  385. // Add order note
  386. $order->add_order_note( sprintf( __( 'Simplify payment approved (ID: %1$s, Auth Code: %2$s)', 'woocommerce' ), $payment->id, $payment->authCode ) );
  387. } else {
  388. return new WP_Error( 'simplify_payment_declined', __( 'Payment was declined - the customer need to try another card.', 'woocommerce' ) );
  389. }
  390. } catch ( Exception $e ) {
  391. $order_note = sprintf( __( 'Simplify Transaction Failed (%s)', 'woocommerce' ), $e->getMessage() );
  392. // Mark order as failed if not already set,
  393. // otherwise, make sure we add the order note so we can detect when someone fails to check out multiple times
  394. if ( 'failed' != $order->get_status() ) {
  395. $order->update_status( 'failed', $order_note );
  396. } else {
  397. $order->add_order_note( $order_note );
  398. }
  399. }
  400. }
  401. /**
  402. * Return handler for Hosted Payments.
  403. */
  404. public function return_handler() {
  405. if ( ! isset( $_REQUEST['cardToken'] ) ) {
  406. parent::return_handler();
  407. }
  408. @ob_clean();
  409. header( 'HTTP/1.1 200 OK' );
  410. $redirect_url = wc_get_page_permalink( 'cart' );
  411. if ( isset( $_REQUEST['reference'] ) && isset( $_REQUEST['amount'] ) ) {
  412. $cart_token = $_REQUEST['cardToken'];
  413. $amount = absint( $_REQUEST['amount'] );
  414. $order_id = absint( $_REQUEST['reference'] );
  415. $order = wc_get_order( $order_id );
  416. $order_total = absint( $order->get_total() * 100 );
  417. if ( $amount === $order_total ) {
  418. if ( $this->order_contains_subscription( $order->get_id() ) ) {
  419. $response = $this->process_subscription( $order, $cart_token );
  420. } elseif ( $this->order_contains_pre_order( $order->get_id() ) ) {
  421. $response = $this->process_pre_order( $order, $cart_token );
  422. } else {
  423. $response = parent::process_standard_payments( $order, $cart_token );
  424. }
  425. if ( 'success' == $response['result'] ) {
  426. $redirect_url = $response['redirect'];
  427. } else {
  428. $order->update_status( 'failed', __( 'Payment was declined by Simplify Commerce.', 'woocommerce' ) );
  429. }
  430. wp_redirect( $redirect_url );
  431. exit();
  432. }
  433. }
  434. wp_redirect( $redirect_url );
  435. exit();
  436. }
  437. }