swpm-stripe-sca-buy-now-ipn.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. <?php
  2. class SwpmStripeSCABuyNowIpnHandler {
  3. public function __construct() {
  4. //check if this is session create request
  5. if ( wp_doing_ajax() ) {
  6. $action = filter_input( INPUT_POST, 'action', FILTER_SANITIZE_STRING );
  7. if ( 'swpm_stripe_sca_create_checkout_session' === $action ) {
  8. add_action( 'wp_ajax_swpm_stripe_sca_create_checkout_session', array( $this, 'handle_session_create' ) );
  9. add_action( 'wp_ajax_nopriv_swpm_stripe_sca_create_checkout_session', array( $this, 'handle_session_create' ) );
  10. }
  11. return;
  12. }
  13. require_once SIMPLE_WP_MEMBERSHIP_PATH . 'ipn/swpm_handle_subsc_ipn.php';
  14. $this->handle_stripe_ipn();
  15. }
  16. public function handle_stripe_ipn() {
  17. SwpmLog::log_simple_debug( 'Stripe SCA Buy Now IPN received. Processing request...', true );
  18. // SwpmLog::log_simple_debug(print_r($_REQUEST, true), true);//Useful for debugging purpose
  19. // Read and sanitize the request parameters.
  20. $ref_id = filter_input( INPUT_GET, 'ref_id', FILTER_SANITIZE_STRING );
  21. if ( empty( $ref_id ) ) {
  22. //no ref id provided, cannot proceed
  23. SwpmLog::log_simple_debug( 'Fatal Error! No ref_id provied.', false );
  24. wp_die( esc_html( 'Fatal Error! No ref_id provied.' ) );
  25. }
  26. $trans_info = explode( '|', $ref_id );
  27. $button_id = isset( $trans_info[1] ) ? absint( $trans_info[1] ) : false;
  28. // Retrieve the CPT for this button
  29. $button_cpt = get_post( $button_id );
  30. if ( ! $button_cpt ) {
  31. // Fatal error. Could not find this payment button post object.
  32. SwpmLog::log_simple_debug( 'Fatal Error! Failed to retrieve the payment button post object for the given button ID: ' . $button_id, false );
  33. wp_die( esc_html( sprintf( 'Fatal Error! Payment button (ID: %d) does not exist. This request will fail.', $button_id ) ) );
  34. }
  35. $settings = SwpmSettings::get_instance();
  36. $sandbox_enabled = $settings->get_value( 'enable-sandbox-testing' );
  37. //API keys
  38. $api_keys = SwpmMiscUtils::get_stripe_api_keys_from_payment_button( $button_id, ! $sandbox_enabled );
  39. // Include the Stripe library.
  40. SwpmMiscUtils::load_stripe_lib();
  41. try {
  42. \Stripe\Stripe::setApiKey( $api_keys['secret'] );
  43. $events = \Stripe\Event::all(
  44. array(
  45. 'type' => 'checkout.session.completed',
  46. 'created' => array(
  47. 'gte' => time() - 60 * 60,
  48. ),
  49. )
  50. );
  51. $sess = false;
  52. foreach ( $events->autoPagingIterator() as $event ) {
  53. $session = $event->data->object;
  54. if ( isset( $session->client_reference_id ) && $session->client_reference_id === $ref_id ) {
  55. $sess = $session;
  56. break;
  57. }
  58. }
  59. if ( false === $sess ) {
  60. // Can't find session.
  61. $error_msg = sprintf( "Fatal error! Payment with ref_id %s can't be found", $ref_id );
  62. SwpmLog::log_simple_debug( $error_msg, false );
  63. wp_die( esc_html( $error_msg ) );
  64. }
  65. $pi_id = $sess->payment_intent;
  66. $pi = \Stripe\PaymentIntent::retrieve( $pi_id );
  67. } catch ( Exception $e ) {
  68. $error_msg = 'Error occurred: ' . $e->getMessage();
  69. SwpmLog::log_simple_debug( $error_msg, false );
  70. wp_die( esc_html( $error_msg ) );
  71. }
  72. $charge = $pi->charges;
  73. // Grab the charge ID and set it as the transaction ID.
  74. $txn_id = $charge->data[0]->id;
  75. // The charge ID can be used to retrieve the transaction details using hte following call.
  76. // \Stripe\Charge::retrieve($charge->$data[0]->id);
  77. //check if this payment has already been processed
  78. $payment = get_posts(
  79. array(
  80. 'meta_key' => 'txn_id',
  81. 'meta_value' => $txn_id,
  82. 'posts_per_page' => 1,
  83. 'offset' => 0,
  84. 'post_type' => 'swpm_transactions',
  85. )
  86. );
  87. wp_reset_postdata();
  88. if ( $payment ) {
  89. //payment has already been processed. Redirecting user to return_url
  90. $return_url = get_post_meta( $button_id, 'return_url', true );
  91. if ( empty( $return_url ) ) {
  92. $return_url = SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL;
  93. }
  94. SwpmMiscUtils::redirect_to_url( $return_url );
  95. return;
  96. }
  97. $price_in_cents = floatval( $pi->amount_received );
  98. $currency_code = strtoupper( $pi->currency );
  99. $zero_cents = unserialize( SIMPLE_WP_MEMBERSHIP_STRIPE_ZERO_CENTS );
  100. if ( in_array( $currency_code, $zero_cents, true ) ) {
  101. $payment_amount = $price_in_cents;
  102. } else {
  103. $payment_amount = $price_in_cents / 100;// The amount (in cents). This value is used in Stripe API.
  104. }
  105. $payment_amount = floatval( $payment_amount );
  106. $stripe_email = $charge->data[0]->billing_details->email;
  107. $membership_level_id = get_post_meta( $button_id, 'membership_level_id', true );
  108. // Validate and verify some of the main values.
  109. $true_payment_amount = get_post_meta( $button_id, 'payment_amount', true );
  110. $true_payment_amount = apply_filters( 'swpm_payment_amount_filter', $true_payment_amount, $button_id );
  111. $true_payment_amount = floatval( $true_payment_amount );
  112. if ( $payment_amount !== $true_payment_amount ) {
  113. // Fatal error. Payment amount may have been tampered with.
  114. $error_msg = 'Fatal Error! Received payment amount (' . $payment_amount . ') does not match with the original amount (' . $true_payment_amount . ')';
  115. SwpmLog::log_simple_debug( $error_msg, false );
  116. wp_die( esc_html( $error_msg ) );
  117. }
  118. $true_currency_code = get_post_meta( $button_id, 'payment_currency', true );
  119. if ( $currency_code !== $true_currency_code ) {
  120. // Fatal error. Currency code may have been tampered with.
  121. $error_msg = 'Fatal Error! Received currency code (' . $currency_code . ') does not match with the original code (' . $true_currency_code . ')';
  122. SwpmLog::log_simple_debug( $error_msg, false );
  123. wp_die( esc_html( $error_msg ) );
  124. }
  125. // Everything went ahead smoothly with the charge.
  126. SwpmLog::log_simple_debug( 'Stripe SCA Buy Now charge successful.', true );
  127. $user_ip = SwpmUtils::get_user_ip_address();
  128. //Custom field data
  129. $custom_field_value = 'subsc_ref=' . $membership_level_id;
  130. $custom_field_value .= '&user_ip=' . $user_ip;
  131. if ( SwpmMemberUtils::is_member_logged_in() ) {
  132. $custom_field_value .= '&swpm_id=' . SwpmMemberUtils::get_logged_in_members_id();
  133. }
  134. $custom_field_value = apply_filters( 'swpm_custom_field_value_filter', $custom_field_value );
  135. $custom = $custom_field_value;
  136. $custom_var = SwpmTransactions::parse_custom_var( $custom );
  137. $swpm_id = isset( $custom_var['swpm_id'] ) ? $custom_var['swpm_id'] : '';
  138. // Let's try to get first_name and last_name from full name
  139. $name = trim( $charge->data[0]->billing_details->name );
  140. $last_name = ( strpos( $name, ' ' ) === false ) ? '' : preg_replace( '#.*\s([\w-]*)$#', '$1', $name );
  141. $first_name = trim( preg_replace( '#' . $last_name . '#', '', $name ) );
  142. // Create the $ipn_data array.
  143. $ipn_data = array();
  144. $ipn_data['mc_gross'] = $payment_amount;
  145. $ipn_data['first_name'] = $first_name;
  146. $ipn_data['last_name'] = $last_name;
  147. $ipn_data['payer_email'] = $stripe_email;
  148. $ipn_data['membership_level'] = $membership_level_id;
  149. $ipn_data['txn_id'] = $txn_id;
  150. $ipn_data['subscr_id'] = $txn_id;/* Set the txn_id as subscriber_id so it is similar to PayPal buy now. Also, it can connect to the profile in the "payments" menu. */
  151. $ipn_data['swpm_id'] = $swpm_id;
  152. $ipn_data['ip'] = $custom_var['user_ip'];
  153. $ipn_data['custom'] = $custom;
  154. $ipn_data['gateway'] = 'stripe-sca';
  155. $ipn_data['status'] = 'completed';
  156. $bd_addr = $charge->data[0]->billing_details->address;
  157. $ipn_data['address_street'] = isset( $bd_addr->line1 ) ? $bd_addr->line1 : '';
  158. $ipn_data['address_city'] = isset( $bd_addr->city ) ? $bd_addr->city : '';
  159. $ipn_data['address_state'] = isset( $bd_addr->state ) ? $bd_addr->state : '';
  160. $ipn_data['address_zipcode'] = isset( $bd_addr->postal_code ) ? $bd_addr->postal_code : '';
  161. $ipn_data['address_country'] = isset( $bd_addr->country ) ? $bd_addr->country : '';
  162. $ipn_data['payment_button_id'] = $button_id;
  163. $ipn_data['is_live'] = ! $sandbox_enabled;
  164. // Handle the membership signup related tasks.
  165. swpm_handle_subsc_signup_stand_alone( $ipn_data, $membership_level_id, $txn_id, $swpm_id );
  166. // Save the transaction record
  167. SwpmTransactions::save_txn_record( $ipn_data );
  168. SwpmLog::log_simple_debug( 'Transaction data saved.', true );
  169. // Trigger the stripe IPN processed action hook (so other plugins can can listen for this event).
  170. do_action( 'swpm_stripe_sca_ipn_processed', $ipn_data );
  171. do_action( 'swpm_payment_ipn_processed', $ipn_data );
  172. // Redirect the user to the return URL (or to the homepage if a return URL is not specified for this payment button).
  173. $return_url = get_post_meta( $button_id, 'return_url', true );
  174. if ( empty( $return_url ) ) {
  175. $return_url = SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL;
  176. }
  177. SwpmLog::log_simple_debug( 'Redirecting customer to: ' . $return_url, true );
  178. SwpmLog::log_simple_debug( 'End of Stripe SCA Buy Now IPN processing.', true, true );
  179. SwpmMiscUtils::redirect_to_url( $return_url );
  180. }
  181. public function handle_session_create() {
  182. $button_id = filter_input( INPUT_POST, 'swpm_button_id', FILTER_SANITIZE_NUMBER_INT );
  183. if ( empty( $button_id ) ) {
  184. wp_send_json( array( 'error' => 'No button ID provided' ) );
  185. }
  186. $uniqid = filter_input( INPUT_POST, 'swpm_uniqid', FILTER_SANITIZE_STRING );
  187. $uniqid = ! empty( $uniqid ) ? $uniqid : '';
  188. $settings = SwpmSettings::get_instance();
  189. $button_cpt = get_post( $button_id ); //Retrieve the CPT for this button
  190. $item_name = htmlspecialchars( $button_cpt->post_title );
  191. $plan_id = get_post_meta( $button_id, 'stripe_plan_id', true );
  192. if ( empty( $plan_id ) ) {
  193. //Payment amount and currency
  194. $payment_amount = get_post_meta( $button_id, 'payment_amount', true );
  195. if ( ! is_numeric( $payment_amount ) ) {
  196. wp_send_json( array( 'error' => 'Error! The payment amount value of the button must be a numeric number. Example: 49.50' ) );
  197. }
  198. $payment_currency = get_post_meta( $button_id, 'payment_currency', true );
  199. $payment_amount = round( $payment_amount, 2 ); //round the amount to 2 decimal place.
  200. $payment_amount = apply_filters( 'swpm_payment_amount_filter', $payment_amount, $button_id );
  201. $zero_cents = unserialize( SIMPLE_WP_MEMBERSHIP_STRIPE_ZERO_CENTS );
  202. if ( in_array( $payment_currency, $zero_cents ) ) {
  203. //this is zero-cents currency, amount shouldn't be multiplied by 100
  204. $price_in_cents = $payment_amount;
  205. } else {
  206. $price_in_cents = $payment_amount * 100; //The amount (in cents). This value is passed to Stripe API.
  207. }
  208. $payment_amount_formatted = SwpmMiscUtils::format_money( $payment_amount, $payment_currency );
  209. }
  210. //$button_image_url = get_post_meta($button_id, 'button_image_url', true);//Stripe doesn't currenty support button image for their standard checkout.
  211. //User's IP address
  212. $user_ip = SwpmUtils::get_user_ip_address();
  213. $_SESSION['swpm_payment_button_interaction'] = $user_ip;
  214. //Custom field data
  215. $custom_field_value = 'subsc_ref=' . $membership_level_id;
  216. $custom_field_value .= '&user_ip=' . $user_ip;
  217. if ( SwpmMemberUtils::is_member_logged_in() ) {
  218. $custom_field_value .= '&swpm_id=' . SwpmMemberUtils::get_logged_in_members_id();
  219. }
  220. $custom_field_value = apply_filters( 'swpm_custom_field_value_filter', $custom_field_value );
  221. //Sandbox settings
  222. $sandbox_enabled = $settings->get_value( 'enable-sandbox-testing' );
  223. //API keys
  224. $api_keys = SwpmMiscUtils::get_stripe_api_keys_from_payment_button( $button_id, ! $sandbox_enabled );
  225. //Billing address
  226. $billing_address = isset( $args['billing_address'] ) ? '1' : '';
  227. //By default don't show the billing address in the checkout form.
  228. //if billing_address parameter is not present in the shortcode, let's check button option
  229. if ( $billing_address === '' ) {
  230. $collect_address = get_post_meta( $button_id, 'stripe_collect_address', true );
  231. if ( $collect_address === '1' ) {
  232. //Collect Address enabled in button settings
  233. $billing_address = 1;
  234. }
  235. }
  236. $ref_id = 'swpm_' . $uniqid . '|' . $button_id;
  237. //Return, cancel, notifiy URLs
  238. if ( empty( $plan_id ) ) {
  239. $notify_url = sprintf( SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL . '/?swpm_process_stripe_sca_buy_now=1&ref_id=%s', $ref_id );
  240. } else {
  241. $notify_url = sprintf( SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL . '/?swpm_process_stripe_sca_subscription=1&ref_id=%s', $ref_id );
  242. }
  243. $current_url_posted = filter_input( INPUT_POST, 'swpm_page_url', FILTER_SANITIZE_URL );
  244. $current_url = ! empty( $current_url_posted ) ? $current_url_posted : SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL;
  245. //prefill member email
  246. $prefill_member_email = $settings->get_value( 'stripe-prefill-member-email' );
  247. if ( $prefill_member_email ) {
  248. $auth = SwpmAuth::get_instance();
  249. $member_email = $auth->get( 'email' );
  250. }
  251. SwpmMiscUtils::load_stripe_lib();
  252. try {
  253. \Stripe\Stripe::setApiKey( $api_keys['secret'] );
  254. if ( empty( $plan_id ) ) {
  255. //this is one-off payment
  256. $opts = array(
  257. 'payment_method_types' => array( 'card' ),
  258. 'client_reference_id' => $ref_id,
  259. 'billing_address_collection' => $billing_address ? 'required' : 'auto',
  260. 'line_items' => array(
  261. array(
  262. 'name' => $item_name,
  263. 'description' => $payment_amount_formatted,
  264. 'amount' => $price_in_cents,
  265. 'currency' => $payment_currency,
  266. 'quantity' => 1,
  267. ),
  268. ),
  269. 'success_url' => $notify_url,
  270. 'cancel_url' => $current_url,
  271. );
  272. } else {
  273. //this is subscription payment
  274. $opts = array(
  275. 'payment_method_types' => array( 'card' ),
  276. 'client_reference_id' => $ref_id,
  277. 'billing_address_collection' => $billing_address ? 'required' : 'auto',
  278. 'subscription_data' => array(
  279. 'items' => array( array( 'plan' => $plan_id ) ),
  280. ),
  281. 'success_url' => $notify_url,
  282. 'cancel_url' => $current_url,
  283. );
  284. $trial_period = get_post_meta( $button_id, 'stripe_trial_period', true );
  285. $trial_period = absint( $trial_period );
  286. if ( $trial_period ) {
  287. $opts['subscription_data']['trial_period_days'] = $trial_period;
  288. }
  289. }
  290. if ( ! empty( $item_logo ) ) {
  291. $opts['line_items'][0]['images'] = array( $item_logo );
  292. }
  293. if ( ! empty( $member_email ) ) {
  294. $opts['customer_email'] = $member_email;
  295. }
  296. $opts = apply_filters( 'swpm_stripe_sca_session_opts', $opts, $button_id );
  297. $session = \Stripe\Checkout\Session::create( $opts );
  298. } catch ( Exception $e ) {
  299. $err = $e->getMessage();
  300. wp_send_json( array( 'error' => 'Error occurred: ' . $err ) );
  301. }
  302. wp_send_json( array( 'session_id' => $session->id ) );
  303. }
  304. }
  305. new SwpmStripeSCABuyNowIpnHandler();