PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-ipn-handler.php

https://gitlab.com/webkod3r/tripolis
PHP | 321 lines | 164 code | 50 blank | 107 comment | 34 complexity | d5b34f60215ab7f935f51af6db629bfc MD5 | raw file
  1. <?php
  2. if ( ! defined( 'ABSPATH' ) ) {
  3. exit;
  4. }
  5. include_once( 'class-wc-gateway-paypal-response.php' );
  6. /**
  7. * Handles responses from PayPal IPN.
  8. */
  9. class WC_Gateway_Paypal_IPN_Handler extends WC_Gateway_Paypal_Response {
  10. /** @var string Receiver email address to validate */
  11. protected $receiver_email;
  12. /**
  13. * Constructor.
  14. *
  15. * @param bool $sandbox
  16. * @param string $receiver_email
  17. */
  18. public function __construct( $sandbox = false, $receiver_email = '' ) {
  19. add_action( 'woocommerce_api_wc_gateway_paypal', array( $this, 'check_response' ) );
  20. add_action( 'valid-paypal-standard-ipn-request', array( $this, 'valid_response' ) );
  21. $this->receiver_email = $receiver_email;
  22. $this->sandbox = $sandbox;
  23. }
  24. /**
  25. * Check for PayPal IPN Response.
  26. */
  27. public function check_response() {
  28. if ( ! empty( $_POST ) && $this->validate_ipn() ) {
  29. $posted = wp_unslash( $_POST );
  30. do_action( 'valid-paypal-standard-ipn-request', $posted );
  31. exit;
  32. }
  33. wp_die( 'PayPal IPN Request Failure', 'PayPal IPN', array( 'response' => 500 ) );
  34. }
  35. /**
  36. * There was a valid response.
  37. * @param array $posted Post data after wp_unslash
  38. */
  39. public function valid_response( $posted ) {
  40. if ( ! empty( $posted['custom'] ) && ( $order = $this->get_paypal_order( $posted['custom'] ) ) ) {
  41. // Lowercase returned variables.
  42. $posted['payment_status'] = strtolower( $posted['payment_status'] );
  43. // Sandbox fix.
  44. if ( isset( $posted['test_ipn'] ) && 1 == $posted['test_ipn'] && 'pending' == $posted['payment_status'] ) {
  45. $posted['payment_status'] = 'completed';
  46. }
  47. WC_Gateway_Paypal::log( 'Found order #' . $order->id );
  48. WC_Gateway_Paypal::log( 'Payment status: ' . $posted['payment_status'] );
  49. if ( method_exists( $this, 'payment_status_' . $posted['payment_status'] ) ) {
  50. call_user_func( array( $this, 'payment_status_' . $posted['payment_status'] ), $order, $posted );
  51. }
  52. }
  53. }
  54. /**
  55. * Check PayPal IPN validity.
  56. */
  57. public function validate_ipn() {
  58. WC_Gateway_Paypal::log( 'Checking IPN response is valid' );
  59. // Get received values from post data
  60. $validate_ipn = array( 'cmd' => '_notify-validate' );
  61. $validate_ipn += wp_unslash( $_POST );
  62. // Send back post vars to paypal
  63. $params = array(
  64. 'body' => $validate_ipn,
  65. 'timeout' => 60,
  66. 'httpversion' => '1.1',
  67. 'compress' => false,
  68. 'decompress' => false,
  69. 'user-agent' => 'WooCommerce/' . WC()->version
  70. );
  71. // Post back to get a response.
  72. $response = wp_safe_remote_post( $this->sandbox ? 'https://www.sandbox.paypal.com/cgi-bin/webscr' : 'https://www.paypal.com/cgi-bin/webscr', $params );
  73. WC_Gateway_Paypal::log( 'IPN Request: ' . print_r( $params, true ) );
  74. WC_Gateway_Paypal::log( 'IPN Response: ' . print_r( $response, true ) );
  75. // Check to see if the request was valid.
  76. if ( ! is_wp_error( $response ) && $response['response']['code'] >= 200 && $response['response']['code'] < 300 && strstr( $response['body'], 'VERIFIED' ) ) {
  77. WC_Gateway_Paypal::log( 'Received valid response from PayPal' );
  78. return true;
  79. }
  80. WC_Gateway_Paypal::log( 'Received invalid response from PayPal' );
  81. if ( is_wp_error( $response ) ) {
  82. WC_Gateway_Paypal::log( 'Error response: ' . $response->get_error_message() );
  83. }
  84. return false;
  85. }
  86. /**
  87. * Check for a valid transaction type.
  88. * @param string $txn_type
  89. */
  90. protected function validate_transaction_type( $txn_type ) {
  91. $accepted_types = array( 'cart', 'instant', 'express_checkout', 'web_accept', 'masspay', 'send_money' );
  92. if ( ! in_array( strtolower( $txn_type ), $accepted_types ) ) {
  93. WC_Gateway_Paypal::log( 'Aborting, Invalid type:' . $txn_type );
  94. exit;
  95. }
  96. }
  97. /**
  98. * Check currency from IPN matches the order.
  99. * @param WC_Order $order
  100. * @param string $currency
  101. */
  102. protected function validate_currency( $order, $currency ) {
  103. if ( $order->get_order_currency() != $currency ) {
  104. WC_Gateway_Paypal::log( 'Payment error: Currencies do not match (sent "' . $order->get_order_currency() . '" | returned "' . $currency . '")' );
  105. // Put this order on-hold for manual checking.
  106. $order->update_status( 'on-hold', sprintf( __( 'Validation error: PayPal currencies do not match (code %s).', 'woocommerce' ), $currency ) );
  107. exit;
  108. }
  109. }
  110. /**
  111. * Check payment amount from IPN matches the order.
  112. * @param WC_Order $order
  113. * @param int $amount
  114. */
  115. protected function validate_amount( $order, $amount ) {
  116. if ( number_format( $order->get_total(), 2, '.', '' ) != number_format( $amount, 2, '.', '' ) ) {
  117. WC_Gateway_Paypal::log( 'Payment error: Amounts do not match (gross ' . $amount . ')' );
  118. // Put this order on-hold for manual checking.
  119. $order->update_status( 'on-hold', sprintf( __( 'Validation error: PayPal amounts do not match (gross %s).', 'woocommerce' ), $amount ) );
  120. exit;
  121. }
  122. }
  123. /**
  124. * Check receiver email from PayPal. If the receiver email in the IPN is different than what is stored in.
  125. * WooCommerce -> Settings -> Checkout -> PayPal, it will log an error about it.
  126. * @param WC_Order $order
  127. * @param string $receiver_email
  128. */
  129. protected function validate_receiver_email( $order, $receiver_email ) {
  130. if ( strcasecmp( trim( $receiver_email ), trim( $this->receiver_email ) ) != 0 ) {
  131. WC_Gateway_Paypal::log( "IPN Response is for another account: {$receiver_email}. Your email is {$this->receiver_email}" );
  132. // Put this order on-hold for manual checking.
  133. $order->update_status( 'on-hold', sprintf( __( 'Validation error: PayPal IPN response from a different email address (%s).', 'woocommerce' ), $receiver_email ) );
  134. exit;
  135. }
  136. }
  137. /**
  138. * Handle a completed payment.
  139. * @param WC_Order $order
  140. * @param array $posted
  141. */
  142. protected function payment_status_completed( $order, $posted ) {
  143. if ( $order->has_status( 'completed' ) ) {
  144. WC_Gateway_Paypal::log( 'Aborting, Order #' . $order->id . ' is already complete.' );
  145. exit;
  146. }
  147. $this->validate_transaction_type( $posted['txn_type'] );
  148. $this->validate_currency( $order, $posted['mc_currency'] );
  149. $this->validate_amount( $order, $posted['mc_gross'] );
  150. $this->validate_receiver_email( $order, $posted['receiver_email'] );
  151. $this->save_paypal_meta_data( $order, $posted );
  152. if ( 'completed' === $posted['payment_status'] ) {
  153. $this->payment_complete( $order, ( ! empty( $posted['txn_id'] ) ? wc_clean( $posted['txn_id'] ) : '' ), __( 'IPN payment completed', 'woocommerce' ) );
  154. if ( ! empty( $posted['mc_fee'] ) ) {
  155. // Log paypal transaction fee.
  156. update_post_meta( $order->id, 'PayPal Transaction Fee', wc_clean( $posted['mc_fee'] ) );
  157. }
  158. } else {
  159. $this->payment_on_hold( $order, sprintf( __( 'Payment pending: %s', 'woocommerce' ), $posted['pending_reason'] ) );
  160. }
  161. }
  162. /**
  163. * Handle a pending payment.
  164. * @param WC_Order $order
  165. * @param array $posted
  166. */
  167. protected function payment_status_pending( $order, $posted ) {
  168. $this->payment_status_completed( $order, $posted );
  169. }
  170. /**
  171. * Handle a failed payment.
  172. * @param WC_Order $order
  173. * @param array $posted
  174. */
  175. protected function payment_status_failed( $order, $posted ) {
  176. $order->update_status( 'failed', sprintf( __( 'Payment %s via IPN.', 'woocommerce' ), wc_clean( $posted['payment_status'] ) ) );
  177. }
  178. /**
  179. * Handle a denied payment.
  180. * @param WC_Order $order
  181. * @param array $posted
  182. */
  183. protected function payment_status_denied( $order, $posted ) {
  184. $this->payment_status_failed( $order, $posted );
  185. }
  186. /**
  187. * Handle an expired payment.
  188. * @param WC_Order $order
  189. * @param array $posted
  190. */
  191. protected function payment_status_expired( $order, $posted ) {
  192. $this->payment_status_failed( $order, $posted );
  193. }
  194. /**
  195. * Handle a voided payment.
  196. * @param WC_Order $order
  197. * @param array $posted
  198. */
  199. protected function payment_status_voided( $order, $posted ) {
  200. $this->payment_status_failed( $order, $posted );
  201. }
  202. /**
  203. * Handle a refunded order.
  204. * @param WC_Order $order
  205. * @param array $posted
  206. */
  207. protected function payment_status_refunded( $order, $posted ) {
  208. // Only handle full refunds, not partial.
  209. if ( $order->get_total() == ( $posted['mc_gross'] * -1 ) ) {
  210. // Mark order as refunded.
  211. $order->update_status( 'refunded', sprintf( __( 'Payment %s via IPN.', 'woocommerce' ), strtolower( $posted['payment_status'] ) ) );
  212. $this->send_ipn_email_notification(
  213. sprintf( __( 'Payment for order %s refunded', 'woocommerce' ), '<a class="link" href="' . esc_url( admin_url( 'post.php?post=' . $order->id . '&action=edit' ) ) . '">' . $order->get_order_number() . '</a>' ),
  214. sprintf( __( 'Order #%s has been marked as refunded - PayPal reason code: %s', 'woocommerce' ), $order->get_order_number(), $posted['reason_code'] )
  215. );
  216. }
  217. }
  218. /**
  219. * Handle a reveral.
  220. * @param WC_Order $order
  221. * @param array $posted
  222. */
  223. protected function payment_status_reversed( $order, $posted ) {
  224. $order->update_status( 'on-hold', sprintf( __( 'Payment %s via IPN.', 'woocommerce' ), wc_clean( $posted['payment_status'] ) ) );
  225. $this->send_ipn_email_notification(
  226. sprintf( __( 'Payment for order %s reversed', 'woocommerce' ), '<a class="link" href="' . esc_url( admin_url( 'post.php?post=' . $order->id . '&action=edit' ) ) . '">' . $order->get_order_number() . '</a>' ),
  227. sprintf( __( 'Order #%s has been marked on-hold due to a reversal - PayPal reason code: %s', 'woocommerce' ), $order->get_order_number(), wc_clean( $posted['reason_code'] ) )
  228. );
  229. }
  230. /**
  231. * Handle a cancelled reveral.
  232. * @param WC_Order $order
  233. * @param array $posted
  234. */
  235. protected function payment_status_canceled_reversal( $order, $posted ) {
  236. $this->send_ipn_email_notification(
  237. sprintf( __( 'Reversal cancelled for order #%s', 'woocommerce' ), $order->get_order_number() ),
  238. sprintf( __( 'Order #%s has had a reversal cancelled. Please check the status of payment and update the order status accordingly here: %s', 'woocommerce' ), $order->get_order_number(), esc_url( admin_url( 'post.php?post=' . $order->id . '&action=edit' ) ) )
  239. );
  240. }
  241. /**
  242. * Save important data from the IPN to the order.
  243. * @param WC_Order $order
  244. * @param array $posted
  245. */
  246. protected function save_paypal_meta_data( $order, $posted ) {
  247. if ( ! empty( $posted['payer_email'] ) ) {
  248. update_post_meta( $order->id, 'Payer PayPal address', wc_clean( $posted['payer_email'] ) );
  249. }
  250. if ( ! empty( $posted['first_name'] ) ) {
  251. update_post_meta( $order->id, 'Payer first name', wc_clean( $posted['first_name'] ) );
  252. }
  253. if ( ! empty( $posted['last_name'] ) ) {
  254. update_post_meta( $order->id, 'Payer last name', wc_clean( $posted['last_name'] ) );
  255. }
  256. if ( ! empty( $posted['payment_type'] ) ) {
  257. update_post_meta( $order->id, 'Payment type', wc_clean( $posted['payment_type'] ) );
  258. }
  259. }
  260. /**
  261. * Send a notification to the user handling orders.
  262. * @param string $subject
  263. * @param string $message
  264. */
  265. protected function send_ipn_email_notification( $subject, $message ) {
  266. $new_order_settings = get_option( 'woocommerce_new_order_settings', array() );
  267. $mailer = WC()->mailer();
  268. $message = $mailer->wrap_message( $subject, $message );
  269. $mailer->send( ! empty( $new_order_settings['recipient'] ) ? $new_order_settings['recipient'] : get_option( 'admin_email' ), strip_tags( $subject ), $message );
  270. }
  271. }