PK!ɗCCswpm-smart-checkout-ipn.phpnu[paypal_url = 'https://www.paypal.com/cgi-bin/webscr'; $this->ipn_log_file = 'ipn_handle_debug_swpm.log'; $this->ipn_response = ''; } public function swpm_validate_and_create_membership() { // Check Product Name , Price , Currency , Receivers email. $error_msg = ''; // Read the IPN and validate. $gross_total = $this->ipn_data['mc_gross']; $transaction_type = $this->ipn_data['txn_type']; $txn_id = $this->ipn_data['txn_id']; $payment_status = $this->ipn_data['payment_status']; // Check payment status. if ( ! empty( $payment_status ) ) { if ( 'Denied' == $payment_status ) { $this->debug_log( 'Payment status for this transaction is DENIED. You denied the transaction... most likely a cancellation of an eCheque. Nothing to do here.', false ); return false; } if ( 'Canceled_Reversal' == $payment_status ) { $this->debug_log( 'This is a dispute closed notification in your favour. The plugin will not do anyting.', false ); return true; } if ( 'Completed' != $payment_status && 'Processed' != $payment_status && 'Refunded' != $payment_status && 'Reversed' != $payment_status ) { $error_msg .= 'Funds have not been cleared yet. Transaction will be processed when the funds clear!'; $this->debug_log( $error_msg, false ); $this->debug_log( wp_json_encode( $this->ipn_data ), false ); return false; } } // Check txn type. if ( 'new_case' == $transaction_type ) { $this->debug_log( 'This is a dispute case. Nothing to do here.', true ); return true; } $custom = urldecode( $this->ipn_data['custom'] ); $this->ipn_data['custom'] = $custom; $customvariables = SwpmTransactions::parse_custom_var( $custom ); // Handle refunds. if ( $gross_total < 0 ) { // This is a refund or reversal. $this->debug_log( 'This is a refund notification. Refund amount: ' . $gross_total, true ); swpm_handle_subsc_cancel_stand_alone( $this->ipn_data, true ); return true; } if ( isset( $this->ipn_data['reason_code'] ) && 'refund' == $this->ipn_data['reason_code'] ) { $this->debug_log( 'This is a refund notification. Refund amount: ' . $gross_total, true ); swpm_handle_subsc_cancel_stand_alone( $this->ipn_data, true ); return true; } if ( ( 'subscr_signup' == $transaction_type ) ) { $this->debug_log( 'Subscription signup IPN received... (handled by the subscription IPN handler)', true ); // Code to handle the signup IPN for subscription. $subsc_ref = $customvariables['subsc_ref']; if ( ! empty( $subsc_ref ) ) { $this->debug_log( 'Found a membership level ID. Creating member account...', true ); $swpm_id = $customvariables['swpm_id']; swpm_handle_subsc_signup_stand_alone( $this->ipn_data, $subsc_ref, $this->ipn_data['subscr_id'], $swpm_id ); // Handle customized subscription signup. } return true; } elseif ( ( 'subscr_cancel' == $transaction_type ) || ( 'subscr_eot' == $transaction_type ) || ( 'subscr_failed' == $transaction_type ) ) { // Code to handle the IPN for subscription cancellation. $this->debug_log( 'Subscription cancellation IPN received... (handled by the subscription IPN handler)', true ); swpm_handle_subsc_cancel_stand_alone( $this->ipn_data ); return true; } else { $cart_items = array(); $this->debug_log( 'Transaction Type: Buy Now/Subscribe', true ); $item_number = $this->ipn_data['item_number']; $item_name = $this->ipn_data['item_name']; $quantity = $this->ipn_data['quantity']; $mc_gross = $this->ipn_data['mc_gross']; $mc_currency = $this->ipn_data['mc_currency']; $current_item = array( 'item_number' => $item_number, 'item_name' => $item_name, 'quantity' => $quantity, 'mc_gross' => $mc_gross, 'mc_currency' => $mc_currency, ); array_push( $cart_items, $current_item ); } $counter = 0; foreach ( $cart_items as $current_cart_item ) { $cart_item_data_num = $current_cart_item['item_number']; $cart_item_data_name = trim( $current_cart_item['item_name'] ); $cart_item_data_quantity = $current_cart_item['quantity']; $cart_item_data_total = $current_cart_item['mc_gross']; $cart_item_data_currency = $current_cart_item['mc_currency']; if ( empty( $cart_item_data_quantity ) ) { $cart_item_data_quantity = 1; } $this->debug_log( 'Item Number: ' . $cart_item_data_num, true ); $this->debug_log( 'Item Name: ' . $cart_item_data_name, true ); $this->debug_log( 'Item Quantity: ' . $cart_item_data_quantity, true ); $this->debug_log( 'Item Total: ' . $cart_item_data_total, true ); $this->debug_log( 'Item Currency: ' . $cart_item_data_currency, true ); // Get the button id. $pp_hosted_button = false; $button_id = $cart_item_data_num; // Button id is the item number. $membership_level_id = get_post_meta( $button_id, 'membership_level_id', true ); if ( ! SwpmUtils::membership_level_id_exists( $membership_level_id ) ) { $this->debug_log( 'This payment button was not created in the plugin. This is a paypal hosted button.', true ); $pp_hosted_button = true; } // Price check. $check_price = true; $msg = ''; $msg = apply_filters( 'swpm_before_price_check_filter', $msg, $current_cart_item ); if ( ! empty( $msg ) && 'price-check-override' == $msg ) {// This filter allows an extension to do a customized version of price check (if needed). $check_price = false; $this->debug_log( 'Price and currency check has been overridden by an addon/extension.', true ); } if ( $check_price && ! $pp_hosted_button ) { // Check according to buy now payment or subscription payment. $button_type = get_post_meta( $button_id, 'button_type', true ); if ( 'pp_smart_checkout' == $button_type ) {// This is a PayPal Smart Checkout type button. $expected_amount = ( get_post_meta( $button_id, 'payment_amount', true ) ) * $cart_item_data_quantity; $expected_amount = round( $expected_amount, 2 ); $expected_amount = apply_filters( 'swpm_payment_amount_filter', $expected_amount, $button_id ); $received_amount = $cart_item_data_total; } else { $this->debug_log( 'Error! Unexpected button type: ' . $button_type, false ); return false; } if ( $received_amount < $expected_amount ) { // Error! amount received is less than expected. This is invalid. $this->debug_log( 'Expected amount: ' . $expected_amount, true ); $this->debug_log( 'Received amount: ' . $received_amount, true ); $this->debug_log( 'Price check failed. Amount received is less than the amount expected. This payment will not be processed.', false ); return false; } } // *** Handle Membership Payment *** // -------------------------------------------------------------------------------------- // ========= Need to find the (level ID) in the custom variable ============ $subsc_ref = $customvariables['subsc_ref']; // Membership level ID. $this->debug_log( 'Membership payment paid for membership level ID: ' . $subsc_ref, true ); if ( ! empty( $subsc_ref ) ) { $swpm_id = ''; if ( isset( $customvariables['swpm_id'] ) ) { $swpm_id = $customvariables['swpm_id']; } if ( 'smart_checkout' == $transaction_type ) { $this->debug_log( 'Transaction type: web_accept. Creating member account...', true ); swpm_handle_subsc_signup_stand_alone( $this->ipn_data, $subsc_ref, $this->ipn_data['txn_id'], $swpm_id ); } } else { $this->debug_log( 'Membership level ID is missing in the payment notification! Cannot process this notification.', false ); } // == End of Membership payment handling == $counter++; } /* * * Do Post payment operation and cleanup * * */ // Save the transaction data. $this->debug_log( 'Saving transaction data to the database table.', true ); $this->ipn_data['gateway'] = 'pp_smart_checkout'; $this->ipn_data['status'] = $this->ipn_data['payment_status']; SwpmTransactions::save_txn_record( $this->ipn_data, $cart_items ); $this->debug_log( 'Transaction data saved.', true ); // Trigger the PayPal IPN processed action hook (so other plugins can can listen for this event). do_action( 'swpm_pp_smart_checkout_ipn_processed', $this->ipn_data ); do_action( 'swpm_payment_ipn_processed', $this->ipn_data ); return true; } public function create_ipn_from_smart_checkout( $data ) { $address_street = $data['payer']['payer_info']['shipping_address']['line1']; if ( isset ( $data[ 'payer' ][ 'payer_info' ][ 'shipping_address' ][ 'line2' ] )){ //If address line 2 is present, add it to the address. $address_street .= ", " . $data[ 'payer' ][ 'payer_info' ][ 'shipping_address' ][ 'line2' ]; } $ipn['custom'] = $data['custom_field']; $ipn['item_number'] = $data['button_id']; $ipn['item_name'] = $data['item_name']; $ipn['pay_id'] = $data['id']; $ipn['create_time'] = $data['create_time']; $ipn['txn_id'] = $data['transactions'][0]['related_resources'][0]['sale']['id']; $ipn['reason_code'] = ! empty( $data['transactions'][0]['related_resources'][0]['sale']['reason_code'] ) ? $data['transactions'][0]['related_resources'][0]['sale']['reason_code'] : ''; $ipn['txn_type'] = 'smart_checkout'; $ipn['payment_status'] = ucfirst( $data['transactions'][0]['related_resources'][0]['sale']['state'] ); $ipn['transaction_subject'] = ''; $ipn['mc_currency'] = $data['transactions'][0]['amount']['currency']; $ipn['mc_gross'] = $data['transactions'][0]['amount']['total']; $ipn['quantity'] = 1; $ipn['receiver_email'] = get_option( 'cart_paypal_email' ); // customer info. $ipn['first_name'] = $data['payer']['payer_info']['first_name']; $ipn['last_name'] = $data['payer']['payer_info']['last_name']; $ipn['payer_email'] = $data['payer']['payer_info']['email']; $ipn['address_street'] = $address_street; $ipn['address_city'] = $data['payer']['payer_info']['shipping_address']['city']; $ipn['address_state'] = $data['payer']['payer_info']['shipping_address']['state']; $ipn['address_zip'] = $data['payer']['payer_info']['shipping_address']['postal_code']; $ipn['address_country'] = $data['payer']['payer_info']['shipping_address']['country_code']; $this->ipn_data = $ipn; return true; } public function validate_ipn_smart_checkout() { if ( $this->sandbox_mode ) { $client_id = get_post_meta( $this->ipn_data['item_number'], 'pp_smart_checkout_test_id', true ); $secret = get_post_meta( $this->ipn_data['item_number'], 'pp_smart_checkout_test_sec', true ); $api_base = 'https://api.sandbox.paypal.com'; } else { $client_id = get_post_meta( $this->ipn_data['item_number'], 'pp_smart_checkout_live_id', true ); $secret = get_post_meta( $this->ipn_data['item_number'], 'pp_smart_checkout_live_sec', true ); $api_base = 'https://api.paypal.com'; } $wp_request_headers = array( 'Accept' => 'application/json', // Ignoring base64_encode() PHPCS warning as it's being properly used in this case. 'Authorization' => 'Basic ' . base64_encode( $client_id . ':' . $secret ), // phpcs:ignore ); $res = wp_remote_request( $api_base . '/v1/oauth2/token', array( 'method' => 'POST', 'headers' => $wp_request_headers, 'body' => 'grant_type=client_credentials', ) ); $code = wp_remote_retrieve_response_code( $res ); if ( 200 !== $code ) { // Some error occured. $body = wp_remote_retrieve_body( $res ); // translators: %1$d is error code; %2$s is error message. return sprintf( __( 'Error occured during payment verification. Error code: %1$d. Message: %2$s', 'simple-membership' ), $code, $body ); } $body = wp_remote_retrieve_body( $res ); $body = json_decode( $body ); $token = $body->access_token; $wp_request_headers = array( 'Accept' => 'application/json', 'Authorization' => 'Bearer ' . $token, ); $res = wp_remote_request( $api_base . '/v1/payments/payment/' . $this->ipn_data['pay_id'], array( 'method' => 'GET', 'headers' => $wp_request_headers, ) ); $code = wp_remote_retrieve_response_code( $res ); if ( 200 !== $code ) { // Some error occured. $body = wp_remote_retrieve_body( $res ); // translators: %1$d is error code; %2$s is error message. return sprintf( __( 'Error occured during payment verification. Error code: %1$d. Message: %2$s', 'simple-membership' ), $code, $body ); } $body = wp_remote_retrieve_body( $res ); $body = json_decode( $body ); // check payment details. if ( $body->transactions[0]->amount->total === $this->ipn_data['mc_gross'] && $body->transactions[0]->amount->currency === $this->ipn_data['mc_currency'] ) { // payment is valid. return true; } else { // payment is invalid. // translators: %1$s is expected amount, %2$s is expected currency. return sprintf( __( 'Payment check failed: invalid amount received. Expected %1$s %2$s, got %3$s %4$s.', 'simple-membership' ), $this->ipn_data['mc_gross'], $this->ipn_data['mc_currency'], $body->transactions[0]->amount->total, $body->transactions[0]->amount->currency ); } } public function debug_log( $message, $success, $end = false ) { SwpmLog::log_simple_debug( $message, $success, $end ); } } function swpm_pp_smart_checkout_ajax_hanlder() { // Start of IPN handling (script execution). // check nonce. $uniqid = isset( $_POST['uniqid'] ) ? sanitize_text_field( stripslashes ( $_POST['uniqid'] ) ) : ''; if ( ! check_ajax_referer( 'swpm-pp-smart-checkout-ajax-nonce-' . $uniqid, 'nonce', false ) ) { wp_send_json( array( 'success' => false, 'errMsg' => __( 'Nonce check failed. Please reload the page.', 'simple-membership' ), ) ); exit; } $data = filter_input( INPUT_POST, 'swpm_pp_smart_checkout_payment_data', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY ); if ( empty( $data ) ) { wp_send_json( array( 'success' => false, 'errMsg' => __( 'Empty payment data received.', 'simple-membership' ), ) ); } $ipn_handler_instance = new swpm_smart_checkout_ipn_handler(); $ipn_data_success = $ipn_handler_instance->create_ipn_from_smart_checkout( $data ); if ( true !== $ipn_data_success ) { // error occured during IPN array creation. wp_send_json( array( 'success' => false, 'errMsg' => $ipn_data_success, ) ); } $settings = SwpmSettings::get_instance(); $debug_enabled = $settings->get_value( 'enable-debug' ); if ( ! empty( $debug_enabled ) ) {// debug is enabled in the system. $debug_log = 'log.txt'; // Debug log file name. $ipn_handler_instance->ipn_log = true; $ipn_handler_instance->ipn_log_file = $debug_log; } $sandbox_enabled = $settings->get_value( 'enable-sandbox-testing' ); if ( ! empty( $sandbox_enabled ) ) { // Sandbox testing enabled. $ipn_handler_instance->paypal_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr'; $ipn_handler_instance->sandbox_mode = true; } $ip = filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_UNSAFE_RAW ); $ip = sanitize_text_field( $ip ); $ipn_handler_instance->debug_log( 'Paypal Smart Checkout Class Initiated by ' . $ip, true ); // Validate the IPN. $res = $ipn_handler_instance->validate_ipn_smart_checkout(); if ( true !== $res ) { wp_send_json( array( 'success' => false, 'errMsg' => $res, ) ); } $ipn_handler_instance->debug_log( 'Creating product Information to send.', true ); if ( ! $ipn_handler_instance->swpm_validate_and_create_membership() ) { $ipn_handler_instance->debug_log( 'IPN product validation failed.', false ); wp_send_json( array( 'success' => false, 'errMsg' => __( 'IPN product validation failed. Check the debug log for more details.', 'simple-membership' ), ) ); } $ipn_handler_instance->debug_log( 'Paypal class finished.', true, true ); wp_send_json( array( 'success' => true ) ); } PK!fWnmUUswpm_handle_subsc_ipn.phpnu[get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}swpm_members_tbl WHERE email = %s", $email ), OBJECT ); // db call ok; no-cache ok. if ( ! $query_db ) { // try to retrieve the member details based on the unique_ref. swpm_debug_log_subsc( 'Could not find any record using the given email address (' . $email . '). Attempting to query database using the unique reference: ' . $unique_ref, true ); if ( ! empty( $unique_ref ) ) { $query_db = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}swpm_members_tbl WHERE subscr_id = %s", $unique_ref ), OBJECT ); // db call ok; no-cache ok. if ( $query_db ) { $swpm_id = $query_db->member_id; swpm_debug_log_subsc( 'Found a match in the member database using unique reference. Member ID: ' . $swpm_id, true ); } else { swpm_debug_log_subsc( 'Did not find a match for an existing member profile for the given reference. This must be a new payment from a new member.', true ); } } else { swpm_debug_log_subsc( 'Unique reference is missing in the notification so we have to assume that this is not a payment for an existing member.', true ); } } else { $swpm_id = $query_db->member_id; swpm_debug_log_subsc( 'Found a match in the member database. Member ID: ' . $swpm_id, true ); } } if ( ! empty( $swpm_id ) ) { // This is payment from an existing member/user. Update the existing member account. swpm_debug_log_subsc( 'Modifying the existing membership profile... Member ID: ' . $swpm_id, true ); //Add the member ID value to the $ipn_data (pass by reference will ensure that we will have it available in our save transaction function). $ipn_data['member_id'] = $swpm_id; // Upgrade the member account. $account_state = 'active'; // This is renewal or upgrade of a previously active account. So the status should be set to active. $resultset = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}swpm_members_tbl WHERE member_id = %d", $swpm_id ), OBJECT ); if ( ! $resultset ) { swpm_debug_log_subsc( 'ERROR! Could not find a member account record for the given Member ID: ' . $swpm_id, false ); return; } $old_membership_level = $resultset->membership_level; $old_account_state = $resultset->account_state; // If the payment is for the same/existing membership level, then this is a renewal. Refresh the start date as appropriate. $args = array( 'swpm_id' => $swpm_id, 'membership_level' => $membership_level, 'old_membership_level' => $old_membership_level, ); $subscription_starts = SwpmMemberUtils::calculate_access_start_date_for_account_update( $args ); $subscription_starts = apply_filters( 'swpm_account_update_subscription_starts', $subscription_starts, $args ); swpm_debug_log_subsc( 'Setting access starts date value to: ' . $subscription_starts, true ); swpm_debug_log_subsc( 'Updating the current membership level (' . $old_membership_level . ') of this member to the newly paid level (' . $membership_level . ')', true ); // Set account status to active, update level to the newly paid level, update access start date, update subsriber ID (if applicable). $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}swpm_members_tbl SET account_state=%s, membership_level=%d,subscription_starts=%s,subscr_id=%s WHERE member_id=%d", $account_state, $membership_level, $subscription_starts, $subscr_id, $swpm_id ) ); // Trigger level changed/updated action hook. do_action( 'swpm_membership_level_changed', array( 'member_id' => $swpm_id, 'from_level' => $old_membership_level, 'to_level' => $membership_level, ) ); //Trigger the account status refreshed action hook. do_action( 'swpm_account_status_refreshed', array( 'member_id' => $swpm_id, 'from' => $old_account_state, 'to' => $account_state, ) ); // Set Email details for the account upgrade notification. $email = $ipn_data['payer_email']; $subject = $settings->get_value( 'upgrade-complete-mail-subject' ); if ( empty( $subject ) ) { $subject = 'Member Account Upgraded'; } $body = $settings->get_value( 'upgrade-complete-mail-body' ); if ( empty( $body ) ) { $body = 'Your account has been upgraded successfully'; } $from_address = $settings->get_value( 'email-from' ); $additional_args = array(); $email_body = SwpmMiscUtils::replace_dynamic_tags( $body, $swpm_id, $additional_args ); $headers = 'From: ' . $from_address . "\r\n"; $subject = apply_filters( 'swpm_email_upgrade_complete_subject', $subject ); $email_body = apply_filters( 'swpm_email_upgrade_complete_body', $email_body ); if ( $settings->get_value( 'disable-email-after-upgrade' ) ) { swpm_debug_log_subsc( 'The disable upgrade email settings is checked. No account upgrade/update email will be sent.', true ); //Nothing to do. } else { SwpmMiscUtils::mail( $email, $subject, $email_body, $headers ); swpm_debug_log_subsc( 'Member upgrade/update completion email successfully sent to: ' . $email, true ); } // End of existing user account upgrade/update. } else { // create new member account. $default_account_status = $settings->get_value( 'default-account-status-after-payment', 'active' ); $data = array(); $data['user_name'] = ''; $data['password'] = ''; $data['first_name'] = $ipn_data['first_name']; $data['last_name'] = $ipn_data['last_name']; $data['email'] = $ipn_data['payer_email']; $data['membership_level'] = $membership_level; $data['subscr_id'] = $subscr_id; $data['gender'] = 'not specified'; $data['address_street'] = $ipn_data['address_street']; $data['address_city'] = $ipn_data['address_city']; $data['address_state'] = $ipn_data['address_state']; $data['address_zipcode'] = isset( $ipn_data['address_zip'] ) ? $ipn_data['address_zip'] : ''; $data['country'] = isset( $ipn_data['address_country'] ) ? $ipn_data['address_country'] : ''; $data['member_since'] = $data['subscription_starts'] = $data['last_accessed'] = SwpmUtils::get_current_date_in_wp_zone(); $data['account_state'] = $default_account_status; $reg_code = uniqid(); $md5_code = md5( $reg_code ); $data['reg_code'] = $md5_code; $data['referrer'] = $data['txn_id'] = ''; $data['last_accessed_from_ip'] = isset( $custom_vars['user_ip'] ) ? $custom_vars['user_ip'] : ''; // Save the users IP address. swpm_debug_log_subsc( 'Creating new member account. Membership level ID: ' . $membership_level . ', Subscriber ID value: ' . $data['subscr_id'], true ); $data = array_filter( $data ); // Remove any null values. $wpdb->insert( "{$wpdb->prefix}swpm_members_tbl", $data ); // Create the member record. $member_id = $wpdb->insert_id; if ( empty( $member_id ) ) { swpm_debug_log_subsc( 'Error! Failed to insert a new member record to the database. This request will fail.', false ); return; } //Add the member ID value to the $ipn_data (pass by reference will ensure that we will have it available in our save transaction function). $ipn_data['member_id'] = $member_id; //Create the signup/registration complete URL for the paid membership. $rego_page_url = $settings->get_value( 'registration-page-url' ); $reg_url = add_query_arg( array( 'member_id' => $member_id, 'code' => $md5_code, ), $rego_page_url ); swpm_debug_log_subsc( 'Member signup URL: ' . $reg_url, true ); $subject = $settings->get_value( 'reg-prompt-complete-mail-subject' ); if ( empty( $subject ) ) { $subject = 'Please complete your registration'; } $body = $settings->get_value( 'reg-prompt-complete-mail-body' ); if ( empty( $body ) ) { $body = "Please use the following link to complete your registration. \n {reg_link}"; } $from_address = $settings->get_value( 'email-from' ); $body = html_entity_decode( $body ); $additional_args = array( 'reg_link' => $reg_url ); $email_body = SwpmMiscUtils::replace_dynamic_tags( $body, $member_id, $additional_args ); $headers = 'From: ' . $from_address . "\r\n"; $subject = apply_filters( 'swpm_email_complete_registration_subject', $subject ); $email_body = apply_filters( 'swpm_email_complete_registration_body', $email_body ); if ( empty( $email_body ) ) { swpm_debug_log_subsc( 'Notice: Member signup (prompt to complete registration) email body has been set empty via the filter hook. No email will be sent.', true ); } else { SwpmMiscUtils::mail( $email, $subject, $email_body, $headers ); swpm_debug_log_subsc( 'Member signup (prompt to complete registration) email successfully sent to: ' . $email, true ); } } } /* * This function will handle the refund notification from PayPal as long as the parent transction ID is present. * It will deactivate the corresponding member account. * It will also mark the transaction as "Refunded" in the transactions list. */ function swpm_handle_refund_using_parent_txn_id( $ipn_data ){ swpm_debug_log_subsc( 'Refund notification - lets see if a member account needs to be deactivated.', true ); //Find the transaction record that matches the parent transaction ID. $parent_txn_id = isset($ipn_data['parent_txn_id']) ? $ipn_data['parent_txn_id'] : ''; if(empty($parent_txn_id)){ swpm_debug_log_subsc("Parent txn id field is empty. cannot process this request.", true); return; } $txn_db_row = SwpmTransactions::get_transaction_row_by_txn_id( $parent_txn_id, true); if( ! $txn_db_row ){ swpm_debug_log_subsc("No transaction record found for the transaction id: " . $parent_txn_id, true); return; } //Mark the transaction as refunded. $txn_row_id = $txn_db_row->id; // Update the postmeta for the corresponding transaction cpt. SwpmTransactions::update_transaction_status( $txn_row_id, 'Refunded' ); //Get the member's ID associated with this transaction $member_id = isset($txn_db_row->member_id) ? $txn_db_row->member_id : ''; if( empty ( $member_id )){ //Lets try to retrieve the member ID using the subscr_id field. $subscr_id = $txn_db_row->subscr_id;//The member account can be pulled up from this subscr_id value. if(empty($subscr_id)){ //If subscr_id value is empty, try using the transaction id as the subscr_id value. $subscr_id = $parent_txn_id; } $member_db_row = SwpmMemberUtils::get_user_by_subsriber_id( $subscr_id ); $member_id = isset($member_db_row->member_id) ? $member_db_row->member_id : ''; } if( empty ( $member_id )){ swpm_debug_log_subsc("No associated member account found for the transaction id: " . $parent_txn_id. ". The member account may have been deleted.", true); return; } //Deactivate the member account. SwpmMemberUtils::update_account_state( $member_id, 'inactive' ); //Set the account status to inactive. swpm_debug_log_subsc( 'Member account deactivated.', true ); } /* * All in one function that can handle notification for refund, cancellation, end of term */ function swpm_handle_subsc_cancel_stand_alone( $ipn_data, $refund = false ) { swpm_debug_log_subsc( "Refund/Cancellation Check - Let's see if a member's profile needs to be updated or deactivated.", true ); global $wpdb; $swpm_id = ''; if ( isset( $ipn_data['custom'] ) && !empty( $ipn_data['custom'] ) ){ $customvariables = SwpmTransactions::parse_custom_var( $ipn_data['custom'] ); $swpm_id = $customvariables['swpm_id']; } if ( ! empty( $swpm_id ) ) { // This IPN has the SWPM ID. Retrieve the member record using member ID. swpm_debug_log_subsc( 'Member ID is present. Retrieving member account from the database. Member ID: ' . $swpm_id, true ); $resultset = SwpmMemberUtils::get_user_by_id( $swpm_id ); } elseif ( isset( $ipn_data['subscr_id'] ) && ! empty( $ipn_data['subscr_id'] ) ) { // This IPN has the subscriber ID. Retrieve the member record using subscr_id. $subscr_id = $ipn_data['subscr_id']; swpm_debug_log_subsc( 'Subscriber ID is present. Retrieving member account from the database. Subscr_id: ' . $subscr_id, true ); $resultset = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}swpm_members_tbl where subscr_id LIKE %s", '%' . $wpdb->esc_like( $subscr_id ) . '%' ), OBJECT ); } else if ( isset($ipn_data['parent_txn_id']) && !empty($ipn_data['parent_txn_id'] )){ // Refund for a one time transaction. Use the parent transaction ID to retrieve the profile. swpm_debug_log_subsc( 'Parent transaction ID is present. Goign to search for member account that might be associated with it. Parent Transaction ID: ' . $ipn_data['parent_txn_id'], true ); $subscr_id = $ipn_data['parent_txn_id']; $resultset = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}swpm_members_tbl where subscr_id LIKE %s", '%' . $wpdb->esc_like( $subscr_id ) . '%' ), OBJECT ); } else { // No member ID or subscriber ID or parent transaction ID found in the IPN data. Return from here. swpm_debug_log_subsc( 'No member ID or subscriber ID or parent transaction ID found in the IPN data. Nothing to do here.', true ); return; } if ( $resultset ) { // We have found a member profile for this notification. $member_id = $resultset->member_id; // First, check if this is a refund notification. if ( $refund ) { // This is a refund (not just a subscription cancellation or end). So deactivate the account regardless and bail. SwpmMemberUtils::update_account_state( $member_id, 'inactive' ); // Set the account status to inactive. swpm_debug_log_subsc( 'Subscription refund notification received! Member account deactivated.', true ); return; } // This is a cancellation or end of subscription term (no refund). // Lets retrieve the membership level and details. $level_id = $resultset->membership_level; swpm_debug_log_subsc( 'Membership level ID of the member is: ' . $level_id, true ); $level_row = SwpmUtils::get_membership_level_row_by_id( $level_id ); $subs_duration_type = $level_row->subscription_duration_type; swpm_debug_log_subsc( 'Subscription duration type: ' . $subs_duration_type, true ); if ( SwpmMembershipLevel::NO_EXPIRY == $subs_duration_type ) { // This is a level with "no expiry" or "until cancelled" duration. swpm_debug_log_subsc( 'This is a level with "no expiry" or "until cancelled" duration', true ); // Deactivate this account as the membership level is "no expiry" or "until cancelled". $account_state = 'inactive'; SwpmMemberUtils::update_account_state( $member_id, $account_state ); swpm_debug_log_subsc( 'Subscription cancellation or end of term received! Member account deactivated. Member ID: ' . $member_id, true ); } elseif ( SwpmMembershipLevel::FIXED_DATE == $subs_duration_type ) { // This is a level with a "fixed expiry date" duration. swpm_debug_log_subsc( 'This is a level with a "fixed expiry date" duration.', true ); swpm_debug_log_subsc( 'Nothing to do here. The account will expire on the fixed set date.', true ); } else { // This is a level with "duration" type expiry (example: 30 days, 1 year etc). subscription_period has the duration/period. $subs_period = $level_row->subscription_period; $subs_period_unit = SwpmMembershipLevel::get_level_duration_type_string( $level_row->subscription_duration_type ); swpm_debug_log_subsc( 'This is a level with "duration" type expiry. Duration period: ' . $subs_period . ', Unit: ' . $subs_period_unit, true ); swpm_debug_log_subsc( 'Nothing to do here. The account will expire after the duration time is over.', true ); // TODO Later as an improvement. If you wanted to segment the members who have unsubscribed, you can set the account status to "unsubscribed" here. // Make sure the cronjob to do expiry check and deactivate the member accounts treat this status as if it is "active". } //Update the swpm_transactions CPT record to mark the subscription as "Cancelled". $swpm_txn_cpt_id = SwpmTransactions::get_original_swpm_txn_cpt_id_by_subscr_id( $subscr_id ); if ( ! empty( $swpm_txn_cpt_id ) ) { swpm_debug_log_subsc( 'Updating the the swpm_transaction CPT record (Post ID: '.$swpm_txn_cpt_id.') to mark the subscription as "cancelled".', true ); update_post_meta( $swpm_txn_cpt_id, 'subscr_status', 'cancelled' ); } // Update attached subcription status. SwpmMemberUtils::set_subscription_data_extra_info( $member_id, 'subscription_status', 'inactive'); // Trigger hook for subscription payment cancelled. $ipn_data['member_id'] = $member_id; do_action( 'swpm_subscription_payment_cancelled', $ipn_data ); } else { swpm_debug_log_subsc( 'No associated active member record found for this notification. The profile may have been updated/attached to another subscription or transaction.', true ); return; } } function swpm_update_member_subscription_start_date_if_applicable( $ipn_data ) { global $wpdb; $email = isset( $ipn_data['payer_email'] ) ? $ipn_data['payer_email'] : ''; $subscr_id = isset( $ipn_data['subscr_id'] ) ? $ipn_data['subscr_id'] : ''; $account_state = SwpmSettings::get_instance()->get_value( 'default-account-status-after-payment', 'active' ); $account_state = apply_filters( 'swpm_account_status_for_subscription_start_date_update', $account_state ); if( empty( $subscr_id ) ) { swpm_debug_log_subsc( 'Subscription ID is empty in the IPN data. A Subscription ID value is required to update the access start date of a profile.', false ); return; } swpm_debug_log_subsc( 'Updating the access start date if applicable for this subscription payment. Subscriber ID: ' . $subscr_id . ', Email: ' . $email . ', Account status: ' . $account_state, true ); // We can also query using the email address or SWPM ID (if present in custom var). //Try to find the profile with the given subscr_id. It will exact match subscr_id or match subscr_id|123 $query_db = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}swpm_members_tbl WHERE subscr_id = %s OR subscr_id LIKE %s", $subscr_id, $subscr_id.'|%' ), OBJECT ); if ( $query_db ) { $swpm_id = $query_db->member_id; $current_primary_level = $query_db->membership_level; swpm_debug_log_subsc( 'Found a record in the member table. The Member ID of the account to check is: ' . $swpm_id . ' Membership Level: ' . $current_primary_level, true ); $ipn_data['member_id'] = $swpm_id; do_action( 'swpm_recurring_payment_received', $ipn_data ); // Hook for recurring payment received. $subscription_starts = SwpmUtils::get_current_date_in_wp_zone(); $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}swpm_members_tbl SET account_state=%s,subscription_starts=%s WHERE member_id=%d", $account_state, $subscription_starts, $swpm_id ) ); swpm_debug_log_subsc( 'Updated the member profile with current date as the subscription start date.', true ); // Lets check to see if the subscriber ID and the subscription start date value was updated correctly. $member_record = SwpmMemberUtils::get_user_by_id( $swpm_id ); swpm_debug_log_subsc( 'Value after update - Subscriber ID: ' . $member_record->subscr_id . ', Start Date: ' . $member_record->subscription_starts, true ); } else { swpm_debug_log_subsc( 'Did not find an existing record in the members table for subscriber ID: ' . $subscr_id, true ); swpm_debug_log_subsc( 'This could be a new subscription payment for a new subscription agreement.', true ); } } function swpm_is_paypal_recurring_payment($payment_data){ $recurring_payment = false; $transaction_type = $payment_data['txn_type']; if ($transaction_type == "recurring_payment") { $recurring_payment = true; } else if ($transaction_type == "subscr_payment") { $item_number = $payment_data['item_number']; $subscr_id = $payment_data['subscr_id']; swpm_debug_log_subsc('Is recurring payment check debug data: ' . $item_number . "|" . $subscr_id, true); $result = SwpmTransactions::get_transaction_row_by_subscr_id($subscr_id); if (isset($result)) { swpm_debug_log_subsc('This subscr_id exists in the transactions db. Recurring payment check flag value is true.', true); $recurring_payment = true; return $recurring_payment; } } if ($recurring_payment) { swpm_debug_log_subsc('Recurring payment check flag value is true.', true); } return $recurring_payment; } function swpm_debug_log_subsc( $message, $success, $end = false ) { SwpmLog::log_simple_debug( $message, $success, $end); } PK!MS##$swpm-stripe-sca-subscription-ipn.phpnu[handle_stripe_ipn(); } public function handle_stripe_ipn() { //This will get executed only for direct post (not webhooks). So it is executed at the time of payment in the browser (via HTTP POST). When the "hook" query arg is not set. //The webhooks are handled by the "swpm-stripe-subscription-ipn.php" script. SwpmLog::log_simple_debug( 'Stripe SCA Subscription IPN (HTTP POST) received. Processing request...', true ); // SwpmLog::log_simple_debug(print_r($_REQUEST, true), true);//Useful for debugging purpose // Read and sanitize the request parameters. $ref_id = isset( $_GET['ref_id'] ) ? sanitize_text_field( stripslashes ( $_GET['ref_id'] ) ) : ''; if ( empty( $ref_id ) ) { //no ref id provided, cannot proceed SwpmLog::log_simple_debug( 'Fatal Error! No ref_id provied.', false ); wp_die( esc_html( 'Fatal Error! No ref_id provied.' ) ); } $trans_info = explode( '|', $ref_id ); $button_id = isset( $trans_info[1] ) ? absint( $trans_info[1] ) : false; // Retrieve the CPT for this button $button_cpt = get_post( $button_id ); if ( ! $button_cpt ) { // Fatal error. Could not find this payment button post object. SwpmLog::log_simple_debug( 'Fatal Error! Failed to retrieve the payment button post object for the given button ID: ' . $button_id, false ); wp_die( esc_html( sprintf( 'Fatal Error! Payment button (ID: %d) does not exist. This request will fail.', $button_id ) ) ); } $settings = SwpmSettings::get_instance(); $sandbox_enabled = $settings->get_value( 'enable-sandbox-testing' ); //API keys $api_keys = SwpmMiscUtils::get_stripe_api_keys_from_payment_button( $button_id, ! $sandbox_enabled ); // Include the Stripe library. SwpmMiscUtils::load_stripe_lib(); try { \Stripe\Stripe::setApiKey( $api_keys['secret'] ); $events = \Stripe\Event::all( array( 'type' => 'checkout.session.completed', 'created' => array( 'gte' => time() - 60 * 60, ), ) ); $sess = false; foreach ( $events->autoPagingIterator() as $event ) { $session = $event->data->object; if ( isset( $session->client_reference_id ) && $session->client_reference_id === $ref_id ) { $sess = $session; break; } } if ( false === $sess ) { // Can't find session. $error_msg = sprintf( "Fatal error! Payment with ref_id %s can't be found", $ref_id ); SwpmLog::log_simple_debug( $error_msg, false ); wp_die( esc_html( $error_msg ) ); return; } $sub_id = isset( $sess->subscription ) ? $sess->subscription : ''; $sub = \Stripe\Subscription::retrieve( $sub_id ); } catch ( Exception $e ) { $error_msg = 'Error occurred: ' . $e->getMessage(); SwpmLog::log_simple_debug( $error_msg, false ); wp_die( esc_html( $error_msg ) ); } $pm = \Stripe\PaymentMethod::retrieve( $sub->default_payment_method ); // Grab the charge ID and set it as the transaction ID. $txn_id = $sub->customer; // The charge ID can be used to retrieve the transaction details using hte following call. // \Stripe\Charge::retrieve($charge->$data[0]->id); //check if this payment has already been processed $payment = get_posts( array( 'meta_key' => 'txn_id', 'meta_value' => $txn_id, 'posts_per_page' => 1, 'offset' => 0, 'post_type' => 'swpm_transactions', ) ); wp_reset_postdata(); if ( $payment ) { //payment has already been processed. Redirecting user to return_url $return_url = get_post_meta( $button_id, 'return_url', true ); if ( empty( $return_url ) ) { $return_url = SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL; } SwpmMiscUtils::redirect_to_url( $return_url ); return; } $price_in_cents = $sub->plan->amount; $currency_code = strtoupper( $sub->plan->currency ); $zero_cents = unserialize( SIMPLE_WP_MEMBERSHIP_STRIPE_ZERO_CENTS ); if ( in_array( $currency_code, $zero_cents, true ) ) { $payment_amount = $price_in_cents; } else { $payment_amount = $price_in_cents / 100;// The amount (in cents). This value is used in Stripe API. } $payment_amount = floatval( $payment_amount ); $payment_amount = apply_filters( 'swpm_payment_amount_filter', $payment_amount, $button_id ); $membership_level_id = get_post_meta( $button_id, 'membership_level_id', true ); // Everything went ahead smoothly with the charge. SwpmLog::log_simple_debug( 'Stripe SCA Subscription charge successful.', true ); $customer = \Stripe\Customer::retrieve( $txn_id ); $stripe_email = $customer->email; $user_ip = SwpmUtils::get_user_ip_address(); //Custom field data $custom_field_value = 'subsc_ref=' . $membership_level_id; $custom_field_value .= '&user_ip=' . $user_ip; if ( SwpmMemberUtils::is_member_logged_in() ) { $custom_field_value .= '&swpm_id=' . SwpmMemberUtils::get_logged_in_members_id(); } $custom_field_value = apply_filters( 'swpm_custom_field_value_filter', $custom_field_value ); $custom = $custom_field_value; //Override custom value if necessary (for subscription webhooks) if ( isset ( $sub_id )){ //This is a subscription payment notificataion. Lets check if the original custom field value is already saved in the CPT for this stripe subscription. $orig_custom_value = SwpmTransactions::get_original_custom_value_for_subscription_payment($sub_id); if ( !empty ($orig_custom_value)){ //Override the custom field value with the original transactions value that can contain additional cookie and session info. $custom = $orig_custom_value; SwpmLog::log_simple_debug( 'Custom field value overriden with original value. Custom: ' . $custom, true ); } } $custom_var = SwpmTransactions::parse_custom_var( $custom ); $swpm_id = isset( $custom_var['swpm_id'] ) ? $custom_var['swpm_id'] : ''; // Let's try to get first_name and last_name from full name $name = $pm->billing_details->name; $last_name = ( strpos( $name, ' ' ) === false ) ? '' : preg_replace( '#.*\s([\w-]*)$#', '$1', $name ); $first_name = trim( preg_replace( '#' . $last_name . '#', '', $name ) ); // Create the $ipn_data array. $ipn_data = array(); $ipn_data['mc_gross'] = $payment_amount; $ipn_data['first_name'] = $first_name; $ipn_data['last_name'] = $last_name; $ipn_data['payer_email'] = $stripe_email; $ipn_data['membership_level'] = $membership_level_id; $ipn_data['txn_id'] = $txn_id; $ipn_data['subscr_id'] = $sub_id; $ipn_data['swpm_id'] = $swpm_id; $ipn_data['ip'] = $custom_var['user_ip']; $ipn_data['custom'] = $custom; $ipn_data['gateway'] = 'stripe-sca-subs'; $ipn_data['status'] = 'subscription created'; $bd_addr = $pm->billing_details->address; $ipn_data['address_street'] = isset( $bd_addr->line1 ) ? $bd_addr->line1 : ''; $ipn_data['address_city'] = isset( $bd_addr->city ) ? $bd_addr->city : ''; $ipn_data['address_state'] = isset( $bd_addr->state ) ? $bd_addr->state : ''; $ipn_data['address_zipcode'] = isset( $bd_addr->postal_code ) ? $bd_addr->postal_code : ''; //Get country value from the Stripe response. It can be a country code or a country name. $country = isset( $bd_addr->country ) ? $bd_addr->country : ''; if( strlen($country) == 2 ){//We have a country code. Let's convert it to country name. $ipn_data['address_country'] = SwpmMiscUtils::get_country_name_by_country_code($country); } else { $ipn_data['address_country'] = $country; } $ipn_data['payment_button_id'] = $button_id; $ipn_data['is_live'] = ! $sandbox_enabled; // Handle the membership signup related tasks. swpm_handle_subsc_signup_stand_alone( $ipn_data, $membership_level_id, $txn_id, $swpm_id ); // Save the transaction record SwpmTransactions::save_txn_record( $ipn_data ); SwpmLog::log_simple_debug( 'Transaction data saved.', true ); // Trigger the stripe IPN processed action hook (so other plugins can can listen for this event). do_action( 'swpm_stripe_sca_ipn_processed', $ipn_data ); do_action( 'swpm_payment_ipn_processed', $ipn_data ); // Redirect the user to the return URL (or to the homepage if a return URL is not specified for this payment button). $return_url = get_post_meta( $button_id, 'return_url', true ); if ( empty( $return_url ) ) { $return_url = SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL; } SwpmLog::log_simple_debug( 'Redirecting customer to: ' . $return_url, true ); SwpmLog::log_simple_debug( 'End of Stripe SCA Subscription IPN processing.', true, true ); SwpmMiscUtils::redirect_to_url( $return_url ); } } new SwpmStripeSCASubscriptionIpnHandler(); PK!swpm-stripe-buy-now-ipn.phpnu[handle_stripe_ipn(); } public function handle_stripe_ipn() { SwpmLog::log_simple_debug( 'Stripe Buy Now IPN received. Processing request...', true ); // SwpmLog::log_simple_debug(print_r($_REQUEST, true), true);//Useful for debugging purpose if( !isset($_REQUEST['item_number']) || !isset($_REQUEST['item_price']) ){ SwpmLog::log_simple_debug( 'The Stripe Buy Now IPN request URL was accessed, but it is missing some required parameters. Aborting...', false ); wp_die( esc_html( 'The Stripe Buy Now IPN request URL was accessed, but it is missing some required parameters. Aborting...' ) ); } // Include the Stripe library. SwpmMiscUtils::load_stripe_lib(); // Read and sanitize the request parameters. $button_id = sanitize_text_field( $_REQUEST['item_number'] ); $button_id = absint( $button_id ); $button_title = sanitize_text_field( $_REQUEST['item_name'] ); $payment_amount = sanitize_text_field( $_REQUEST['item_price'] ); $currency_code = sanitize_text_field( $_REQUEST['currency_code'] ); $zero_cents = unserialize( SIMPLE_WP_MEMBERSHIP_STRIPE_ZERO_CENTS ); if ( in_array( $currency_code, $zero_cents ) ) { $price_in_cents = $payment_amount; } else { $price_in_cents = $payment_amount * 100;// The amount (in cents). This value is used in Stripe API. } $stripe_token = isset( $_POST['stripeToken'] ) ? sanitize_text_field( stripslashes ( $_POST['stripeToken'] ) ) : ''; $stripe_token_type = isset( $_POST['stripeTokenType'] ) ? sanitize_text_field( stripslashes ( $_POST['stripeTokenType'] ) ) : ''; $stripe_email = filter_input( INPUT_POST, 'stripeEmail', FILTER_SANITIZE_EMAIL ); // Retrieve the CPT for this button $button_cpt = get_post( $button_id ); if ( ! $button_cpt ) { // Fatal error. Could not find this payment button post object. SwpmLog::log_simple_debug( 'Fatal Error! Failed to retrieve the payment button post object for the given button ID: ' . $button_id, false ); wp_die( esc_html( sprintf( 'Fatal Error! Payment button (ID: %d) does not exist. This request will fail.', $button_id ) ) ); } $membership_level_id = get_post_meta( $button_id, 'membership_level_id', true ); // Validate and verify some of the main values. $true_payment_amount = get_post_meta( $button_id, 'payment_amount', true ); $true_payment_amount = apply_filters( 'swpm_payment_amount_filter', $true_payment_amount, $button_id ); if ( $payment_amount != $true_payment_amount ) { // Fatal error. Payment amount may have been tampered with. $error_msg = 'Fatal Error! Received payment amount (' . $payment_amount . ') does not match with the original amount (' . $true_payment_amount . ')'; SwpmLog::log_simple_debug( $error_msg, false ); wp_die( esc_html( $error_msg ) ); } $true_currency_code = get_post_meta( $button_id, 'payment_currency', true ); if ( $currency_code != $true_currency_code ) { // Fatal error. Currency code may have been tampered with. $error_msg = 'Fatal Error! Received currency code (' . $currency_code . ') does not match with the original code (' . $true_currency_code . ')'; SwpmLog::log_simple_debug( $error_msg, false ); wp_die( esc_html( $error_msg ) ); } // Validation passed. Go ahead with the charge. // Sandbox and other settings $settings = SwpmSettings::get_instance(); $sandbox_enabled = $settings->get_value( 'enable-sandbox-testing' ); //API keys $api_keys = SwpmMiscUtils::get_stripe_api_keys_from_payment_button( $button_id, ! $sandbox_enabled ); // Set secret API key in the Stripe library \Stripe\Stripe::setApiKey( $api_keys['secret'] ); // Get the credit card details submitted by the form $token = $stripe_token; // Create the charge on Stripe's servers - this will charge the user's card try { $charge = \Stripe\Charge::create( array( 'amount' => $price_in_cents, // Amount in cents 'currency' => strtolower( $currency_code ), 'source' => $token, 'description' => $button_title, 'receipt_email' => $stripe_email, ) ); } catch ( \Stripe\Error\Card $e ) { // The card has been declined SwpmLog::log_simple_debug( 'Stripe Charge Error! The card has been declined. ' . $e->getMessage(), false ); $body = $e->getJsonBody(); $error = $body['error']; $error_string = print_r( $error, true ); SwpmLog::log_simple_debug( 'Error details: ' . $error_string, false ); wp_die( esc_html( 'Stripe Charge Error! Card charge has been declined. ' . $e->getMessage() . $error_string ) ); } // Everything went ahead smoothly with the charge. SwpmLog::log_simple_debug( 'Stripe Buy Now charge successful.', true ); // Grab the charge ID and set it as the transaction ID. $txn_id = $charge->id;// $charge->balance_transaction; // The charge ID can be used to retrieve the transaction details using hte following call. // \Stripe\Charge::retrieve($charge->id); $custom = sanitize_text_field( $_REQUEST['custom'] ); $custom_var = SwpmTransactions::parse_custom_var( $custom ); $swpm_id = isset( $custom_var['swpm_id'] ) ? $custom_var['swpm_id'] : ''; // Create the $ipn_data array. $ipn_data = array(); $ipn_data['mc_gross'] = $payment_amount; $ipn_data['first_name'] = ''; $ipn_data['last_name'] = ''; $ipn_data['payer_email'] = $stripe_email; $ipn_data['membership_level'] = $membership_level_id; $ipn_data['txn_id'] = $txn_id; $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. */ $ipn_data['swpm_id'] = $swpm_id; $ipn_data['ip'] = $custom_var['user_ip']; $ipn_data['custom'] = $custom; $ipn_data['gateway'] = 'stripe'; $ipn_data['status'] = 'completed'; $ipn_data['address_street'] = ''; $ipn_data['address_city'] = ''; $ipn_data['address_state'] = ''; $ipn_data['address_zipcode'] = ''; $ipn_data['country'] = ''; // Handle the membership signup related tasks. swpm_handle_subsc_signup_stand_alone( $ipn_data, $membership_level_id, $txn_id, $swpm_id ); // Save the transaction record SwpmTransactions::save_txn_record( $ipn_data ); SwpmLog::log_simple_debug( 'Transaction data saved.', true ); // Trigger the stripe IPN processed action hook (so other plugins can can listen for this event). do_action( 'swpm_stripe_ipn_processed', $ipn_data ); do_action( 'swpm_payment_ipn_processed', $ipn_data ); // Redirect the user to the return URL (or to the homepage if a return URL is not specified for this payment button). $return_url = get_post_meta( $button_id, 'return_url', true ); if ( empty( $return_url ) ) { $return_url = SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL; } SwpmLog::log_simple_debug( 'Redirecting customer to: ' . $return_url, true ); SwpmLog::log_simple_debug( 'End of Stripe Buy Now IPN processing.', true, true ); SwpmMiscUtils::redirect_to_url( $return_url ); } } $swpm_stripe_buy_ipn = new SwpmStripeBuyNowIpnHandler(); PK!j722swpm-stripe-sca-buy-now-ipn.phpnu[handle_stripe_ipn(); } public function handle_stripe_ipn() { //Stripe API upgrade change log - https://stripe.com/docs/upgrades SwpmLog::log_simple_debug( 'Stripe SCA Buy Now IPN received. Processing request...', true ); //SwpmLog::log_simple_debug(print_r($_REQUEST, true), true);//Useful for debugging purpose // Read and sanitize the request parameters. $ref_id = isset( $_GET['ref_id'] ) ? sanitize_text_field( stripslashes ( $_GET['ref_id'] ) ) : ''; if ( empty( $ref_id ) ) { //no ref id provided, cannot proceed SwpmLog::log_simple_debug( 'Fatal Error! No ref_id provied.', false ); wp_die( esc_html( 'Fatal Error! No ref_id provied.' ) ); } $trans_info = explode( '|', $ref_id ); $button_id = isset( $trans_info[1] ) ? absint( $trans_info[1] ) : false; // Retrieve the CPT for this button $button_cpt = get_post( $button_id ); if ( ! $button_cpt ) { // Fatal error. Could not find this payment button post object. SwpmLog::log_simple_debug( 'Fatal Error! Failed to retrieve the payment button post object for the given button ID: ' . $button_id, false ); wp_die( esc_html( sprintf( 'Fatal Error! Payment button (ID: %d) does not exist. This request will fail.', $button_id ) ) ); } //Initialize the discount amount variables to 0. These will be used to set and calculate values (if discount was applied to this transaction). $discount_amount = 0; $discount_amount_in_cents = 0; // Check if the sandbox mode is enabled $settings = SwpmSettings::get_instance(); $sandbox_enabled = $settings->get_value( 'enable-sandbox-testing' ); //API keys $api_keys = SwpmMiscUtils::get_stripe_api_keys_from_payment_button( $button_id, ! $sandbox_enabled ); // Include the Stripe library. SwpmMiscUtils::load_stripe_lib(); try { \Stripe\Stripe::setApiKey( $api_keys['secret'] ); $events = \Stripe\Event::all( array( 'type' => 'checkout.session.completed', 'created' => array( 'gte' => time() - 60 * 60, ), ) ); $sess = false; foreach ( $events->autoPagingIterator() as $event ) { $session = $event->data->object; if ( isset( $session->client_reference_id ) && $session->client_reference_id === $ref_id ) { $sess = $session; break; } } if ( false === $sess ) { // Can't find session. $error_msg = sprintf( "Fatal error! Payment with ref_id %s can't be found", $ref_id ); SwpmLog::log_simple_debug( $error_msg, false ); wp_die( esc_html( $error_msg ) ); } $pi_id = $sess->payment_intent; $pi = \Stripe\PaymentIntent::retrieve( $pi_id ); // Check if any coupon/promo code was applied if ($sess instanceof \Stripe\Checkout\Session && isset($sess->allow_promotion_codes) && $sess->allow_promotion_codes == '1') { if (isset($sess->total_details) && isset($sess->total_details->amount_discount)) { $discount_amount_in_cents = floatval($sess->total_details->amount_discount); SwpmLog::log_simple_debug( 'Discount amount (in cents) applied to this Stripe checkout session is: ' . $discount_amount_in_cents . ' (amount in cents).', true ); } } } catch ( Exception $e ) { $error_msg = 'Error occurred: ' . $e->getMessage(); SwpmLog::log_simple_debug( $error_msg, false ); wp_die( esc_html( $error_msg ) ); } //Get the charge object based on the Stripe API version used in the payment intents object. if( isset ( $pi->latest_charge ) ){ //Using the new Stripe API version 2022-11-15 or later SwpmLog::log_simple_debug( 'Using the Stripe API version 2022-11-15 or later for Payment Intents object. Need to retrieve the charge object.', true ); $charge_id = $pi->latest_charge; //For Stripe API version 2022-11-15 or later, the charge object is not included in the payment intents object. It needs to be retrieved using the charge ID. try { //Retrieve the charge object using the charge ID $charge = \Stripe\Charge::retrieve($charge_id); } catch (\Stripe\Exception\ApiErrorException $e) { // Handle the error SwpmLog::log_simple_debug( 'Stripe error occurred trying to retrieve the charge object using the charge ID. ' . $e->getMessage(), false ); exit; } } else { //Using the old Stripe API version 2022-08-01 or earlier $charge = $pi->charges; $charge = $pi->charges->data[0]; $charge_id = $charge->id; //The old method that is not needed anymore as we will read it from the charge object below. // $stripe_email = $charge->data[0]->billing_details->email; // $name = trim( $charge->data[0]->billing_details->name ); // $bd_addr = $charge->data[0]->billing_details->address; } //Get the email, name and address from the charge object. $stripe_email = $charge->billing_details->email; $name = trim( $charge->billing_details->name ); $bd_addr = $charge->billing_details->address; SwpmLog::log_simple_debug( "Email: " . $stripe_email . ", Name: " . $name . ", Charge ID: " . $charge_id, true ); // Grab the charge ID and set it as the transaction ID. $txn_id = $charge_id;//The charge ID. //check if this payment has already been processed $payment = get_posts( array( 'meta_key' => 'txn_id', 'meta_value' => $txn_id, 'posts_per_page' => 1, 'offset' => 0, 'post_type' => 'swpm_transactions', ) ); wp_reset_postdata(); if ( $payment ) { //payment has already been processed. Redirecting user to return_url $return_url = get_post_meta( $button_id, 'return_url', true ); if ( empty( $return_url ) ) { $return_url = SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL; } SwpmMiscUtils::redirect_to_url( $return_url ); return; } $price_in_cents = floatval( $pi->amount_received ); $currency_code = strtoupper( $pi->currency ); //Check and convert the amounts to dollars or equivalent (if needed). $zero_cents_currency = unserialize( SIMPLE_WP_MEMBERSHIP_STRIPE_ZERO_CENTS ); if ( in_array( $currency_code, $zero_cents_currency, true ) ) { // No decimal (zero cents) currency. Use the amount as is. $payment_amount = $price_in_cents; $discount_amount = $discount_amount_in_cents; } else { // Currency has decimal. Convert the amount to dollars or equivalent. $payment_amount = $price_in_cents / 100; $discount_amount = $discount_amount_in_cents / 100; } //Round to 2 decimal places. Round should be used when math operations are involved. We will compare the payment amount with the expected amount. $payment_amount = floatval( $payment_amount ); $payment_amount = round( $payment_amount, 2 ); $membership_level_id = get_post_meta( $button_id, 'membership_level_id', true ); // === Validate and verify some of the main values === $expected_amount = get_post_meta( $button_id, 'payment_amount', true ); // Check if this button has Stripe promo code enabled $allow_promotion_codes = get_post_meta( $button_id, 'allow_promotion_codes', true ); if ( !empty($allow_promotion_codes) && $allow_promotion_codes == '1' ) { // Promo code option is enabled for this button. Let's decuct any discount (if it was applied in the transation). $expected_amount = $expected_amount - $discount_amount; SwpmLog::log_simple_debug( 'Promo code is enabled for this button. Setting expected payment amount to: ' . $expected_amount . '. Discount amount: ' . $discount_amount, true ); } $expected_amount = apply_filters( 'swpm_payment_amount_filter', $expected_amount, $button_id ); //Round to 2 decimal places. Round should be used when math operations are involved. We will use this amount for comparison. $expected_amount = floatval( $expected_amount );//Convert to float (in case it is a string) $expected_amount = round( $expected_amount, 2 ); SwpmLog::log_simple_debug( 'Expected payment amount for this transaction: ' . $expected_amount, true ); // Check if the payment amount matches the expected amount //Since floating-point numbers are not always stored exactly as they appear due to their binary representation, we will use a precision tolerance. $precision = 0.01;//our precision tolerance. if (abs($expected_amount - $payment_amount) >= $precision) { //The difference is greater than the precision value. //Fatal error. Payment amount may have been tampered with. //Alternatively, we can also use ($payment_amount < $expected_amount) expression. $error_msg = 'Fatal Error! Received payment amount (' . $payment_amount . ') does not match with the expected amount (' . $expected_amount . ')'; SwpmLog::log_simple_debug( $error_msg, false ); wp_die( esc_html( $error_msg ) ); } $expected_currency_code = get_post_meta( $button_id, 'payment_currency', true ); if ( $currency_code !== $expected_currency_code ) { // Fatal error. Currency code may have been tampered with. $error_msg = 'Fatal Error! Received currency code (' . $currency_code . ') does not match with the expected currency code (' . $expected_currency_code . ')'; SwpmLog::log_simple_debug( $error_msg, false ); wp_die( esc_html( $error_msg ) ); } //=== End of validation and verification === // Everything went ahead smoothly with the charge. SwpmLog::log_simple_debug( 'Stripe SCA Buy Now charge successful.', true ); $user_ip = SwpmUtils::get_user_ip_address(); //Custom field data $custom_field_value = 'subsc_ref=' . $membership_level_id; $custom_field_value .= '&user_ip=' . $user_ip; if ( SwpmMemberUtils::is_member_logged_in() ) { $custom_field_value .= '&swpm_id=' . SwpmMemberUtils::get_logged_in_members_id(); } $custom_field_value = apply_filters( 'swpm_custom_field_value_filter', $custom_field_value ); $custom = $custom_field_value; $custom_var = SwpmTransactions::parse_custom_var( $custom ); $swpm_id = isset( $custom_var['swpm_id'] ) ? $custom_var['swpm_id'] : ''; // Let's try to get first_name and last_name from full name $last_name = ( strpos( $name, ' ' ) === false ) ? '' : preg_replace( '#.*\s([\w-]*)$#', '$1', $name ); $first_name = trim( preg_replace( '#' . $last_name . '#', '', $name ) ); // Create the $ipn_data array. $ipn_data = array(); $ipn_data['mc_gross'] = $payment_amount; $ipn_data['first_name'] = $first_name; $ipn_data['last_name'] = $last_name; $ipn_data['payer_email'] = $stripe_email; $ipn_data['membership_level'] = $membership_level_id; $ipn_data['txn_id'] = $txn_id; $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. */ $ipn_data['swpm_id'] = $swpm_id; $ipn_data['ip'] = $custom_var['user_ip']; $ipn_data['custom'] = $custom; $ipn_data['gateway'] = 'stripe-sca'; $ipn_data['status'] = 'completed'; $ipn_data['address_street'] = isset( $bd_addr->line1 ) ? $bd_addr->line1 : ''; $ipn_data['address_city'] = isset( $bd_addr->city ) ? $bd_addr->city : ''; $ipn_data['address_state'] = isset( $bd_addr->state ) ? $bd_addr->state : ''; $ipn_data['address_zipcode'] = isset( $bd_addr->postal_code ) ? $bd_addr->postal_code : ''; //Get country value from the Stripe response. It can be a country code or a country name. $country = isset( $bd_addr->country ) ? $bd_addr->country : ''; if( strlen($country) == 2 ){//We have a country code. Let's convert it to country name. $ipn_data['address_country'] = SwpmMiscUtils::get_country_name_by_country_code($country); } else { $ipn_data['address_country'] = $country; } $ipn_data['payment_button_id'] = $button_id; $ipn_data['is_live'] = ! $sandbox_enabled; $ipn_data['discount_amount'] = $discount_amount;//Discount amount applied to the payment. // Handle the membership signup related tasks. swpm_handle_subsc_signup_stand_alone( $ipn_data, $membership_level_id, $txn_id, $swpm_id ); // Save the transaction record SwpmTransactions::save_txn_record( $ipn_data ); SwpmLog::log_simple_debug( 'Transaction data saved.', true ); // Trigger the stripe IPN processed action hook (so other plugins can can listen for this event). do_action( 'swpm_stripe_sca_ipn_processed', $ipn_data ); do_action( 'swpm_payment_ipn_processed', $ipn_data ); // Redirect the user to the return URL (or to the homepage if a return URL is not specified for this payment button). $return_url = get_post_meta( $button_id, 'return_url', true ); if ( empty( $return_url ) ) { $return_url = SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL; } SwpmLog::log_simple_debug( 'Redirecting customer to: ' . $return_url, true ); SwpmLog::log_simple_debug( 'End of Stripe SCA Buy Now IPN processing.', true, true ); SwpmMiscUtils::redirect_to_url( $return_url ); } } new SwpmStripeSCABuyNowIpnHandler(); PK!m/!!+swpm-stripe-sca-checkout-session-create.phpnu[ 'No button ID provided' ) ); } SwpmLog::log_simple_debug( 'Stripe SCA checkout session create request received. Processing request...', true ); //Check if payment_method_types is being used in the shortcode. $payment_method_types = isset( $_POST['payment_method_types'] ) ? sanitize_text_field( stripslashes ( $_POST['payment_method_types'] ) ) : ''; if ( empty( $payment_method_types ) ) { //Use the empty value so it can be managed from the seller's Stripe account settings. $payment_method_types_array = array(); //$payment_method_types_array = array( 'card' );//Legacy value } else { //Use the payment_method_types specified in the shortcode (example value: card,us_bank_account $payment_method_types_array = array_map( 'trim', explode (",", $payment_method_types) ); } $uniqid = isset( $_POST['swpm_uniqid'] ) ? sanitize_text_field( stripslashes ( $_POST['swpm_uniqid'] ) ) : ''; $uniqid = ! empty( $uniqid ) ? $uniqid : ''; $settings = SwpmSettings::get_instance(); $button_cpt = get_post( $button_id ); //Retrieve the CPT for this button $item_name = htmlspecialchars( $button_cpt->post_title ); $plan_id = get_post_meta( $button_id, 'stripe_plan_id', true ); if ( empty( $plan_id ) ) { //Payment amount and currency $payment_amount = get_post_meta( $button_id, 'payment_amount', true ); if ( ! is_numeric( $payment_amount ) ) { wp_send_json( array( 'error' => 'Error! The payment amount value of the button must be a numeric number. Example: 49.50' ) ); } $payment_currency = get_post_meta( $button_id, 'payment_currency', true ); $payment_amount = round( $payment_amount, 2 ); //round the amount to 2 decimal place. $payment_amount = apply_filters( 'swpm_payment_amount_filter', $payment_amount, $button_id ); $zero_cents = unserialize( SIMPLE_WP_MEMBERSHIP_STRIPE_ZERO_CENTS ); if ( in_array( $payment_currency, $zero_cents ) ) { //this is zero-cents currency, amount shouldn't be multiplied by 100 $price_in_cents = $payment_amount; } else { $price_in_cents = $payment_amount * 100; //The amount (in cents). This value is passed to Stripe API. } $payment_amount_formatted = SwpmMiscUtils::format_money( $payment_amount, $payment_currency ); } //$button_image_url = get_post_meta($button_id, 'button_image_url', true);//Stripe doesn't currenty support button image for their standard checkout. //User's IP address $user_ip = SwpmUtils::get_user_ip_address(); $_SESSION['swpm_payment_button_interaction'] = $user_ip; //Get the button's level ID $membership_level_id = get_post_meta( $button_id, 'membership_level_id', true ); //Custom field data $custom_field_value = 'subsc_ref=' . $membership_level_id; $custom_field_value .= '&user_ip=' . $user_ip; if ( SwpmMemberUtils::is_member_logged_in() ) { $custom_field_value .= '&swpm_id=' . SwpmMemberUtils::get_logged_in_members_id(); } $custom_field_value = apply_filters( 'swpm_custom_field_value_filter', $custom_field_value ); //Sandbox settings $sandbox_enabled = $settings->get_value( 'enable-sandbox-testing' ); //API keys $api_keys = SwpmMiscUtils::get_stripe_api_keys_from_payment_button( $button_id, ! $sandbox_enabled ); //Billing address $billing_address = isset( $args['billing_address'] ) ? '1' : ''; //By default don't show the billing address in the checkout form. //if billing_address parameter is not present in the shortcode, let's check button option if ( $billing_address === '' ) { $collect_address = get_post_meta( $button_id, 'stripe_collect_address', true ); if ( $collect_address === '1' ) { //Collect Address enabled in button settings $billing_address = 1; } } $automatic_tax = false; $automatic_tax_opt = get_post_meta( $button_id, 'stripe_automatic_tax', true ); if ( $automatic_tax_opt === '1' ) { $automatic_tax = true; } $ref_id = 'swpm_' . $uniqid . '|' . $button_id; //Return, cancel, notifiy URLs if ( empty( $plan_id ) ) { $notify_url = sprintf( SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL . '/?swpm_process_stripe_sca_buy_now=1&ref_id=%s', $ref_id ); } else { $notify_url = sprintf( SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL . '/?swpm_process_stripe_sca_subscription=1&ref_id=%s', $ref_id ); } // The url to redirect to when user clicks on the back button in the stripe sca buy now button checkout page. If no url set, there will be no back button. $cancel_url = get_post_meta( $button_id, 'cancel_url', true ); $cancel_url = ! empty( $cancel_url ) ? sanitize_text_field($cancel_url) : SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL; //prefill member email $prefill_member_email = $settings->get_value( 'stripe-prefill-member-email' ); if ( $prefill_member_email ) { $auth = SwpmAuth::get_instance(); $member_email = $auth->get( 'email' ); } SwpmMiscUtils::load_stripe_lib(); try { \Stripe\Stripe::setApiKey( $api_keys['secret'] ); \Stripe\Stripe::setApiVersion("2022-08-01"); if ( empty( $plan_id ) ) { //this is one-off payment $opts = array( 'client_reference_id' => $ref_id, 'billing_address_collection' => $billing_address ? 'required' : 'auto', 'line_items' => array( array( 'price_data' => array( 'currency' => $payment_currency, 'unit_amount' => $price_in_cents, 'product_data' => array( 'name' => $item_name, 'description' => $payment_amount_formatted, ), ), 'quantity' => 1 ) ), 'mode' => 'payment', 'success_url' => $notify_url, 'cancel_url' => $cancel_url, ); } else { //this is subscription payment $opts = array( 'client_reference_id' => $ref_id, 'billing_address_collection' => $billing_address ? 'required' : 'auto', 'line_items' => array( array( 'price' => $plan_id, 'quantity' => 1 ), ), 'mode' => 'subscription', 'success_url' => $notify_url, 'cancel_url' => $cancel_url, ); $trial_period = get_post_meta( $button_id, 'stripe_trial_period', true ); $trial_period = absint( $trial_period ); if ( $trial_period ) { $opts['subscription_data']['trial_period_days'] = $trial_period; } } //Set payment method types (if used in the shortcode). Otherwise, let Stripe use the default value. if( !empty( $payment_method_types_array ) ) { $opts['payment_method_types'] = $payment_method_types_array; } //Set the logo for the line item. if ( ! empty( $item_logo ) ) { $opts['line_items'][0]["product_data"]['images'] = array( $item_logo ); } //Set the customer email. if ( ! empty( $member_email ) ) { $opts['customer_email'] = $member_email; } //Set the automatic tax feature. if( $automatic_tax == true ) { $opts["automatic_tax"] = array( "enabled" => true ); } $allow_promotion_codes = get_post_meta( $button_id, 'allow_promotion_codes', true ); if ( !empty($allow_promotion_codes) && $allow_promotion_codes == '1' ) { $opts["allow_promotion_codes"] = true; } $opts = apply_filters( 'swpm_stripe_sca_session_opts', $opts, $button_id ); $session = \Stripe\Checkout\Session::create( $opts ); } catch ( Exception $e ) { $err = $e->getMessage(); wp_send_json( array( 'error' => 'Error occurred: ' . $err ) ); } SwpmLog::log_simple_debug( 'Stripe SCA checkout session created successfully.', true ); wp_send_json( array( 'session_id' => $session->id ) ); } } new SwpmStripeCheckoutSessionCreate(); PK!44 swpm-stripe-subscription-ipn.phpnu[handle_stripe_ipn(); } public function handle_stripe_ipn() { /* * [Important] This comment explains how this script handles both the first time HTTP Post after payment and the webhooks. * If the "hook" query arg is set then that means it is a webhook notification. It will be used for certain actions like (update, cancel, refund, etc). Others will be ignored. * The first time payment in browser is handled via HTTP POST (when the "hook" query arg is not set). */ if ( isset( $_GET['hook'] ) ) { // This is Webhook notification from Stripe. // This webhook is used for all recurring payment notification (Legacy and SCA ones). // TODO: add Webhook Signing Secret verification // To do this, we need to get customer ID, retreive its details from Stripe, get button_id from metadata // and see if the button has Signing Secret option set. If it is - we need to check signatures // More details here: https://stripe.com/docs/webhooks#signatures $input = @file_get_contents( 'php://input' ); if ( empty( $input ) ) { SwpmLog::log_simple_debug( 'Stripe subscription webhook sent empty data or page was accessed directly. Aborting.', false ); echo 'Empty Webhook data received.'; die; } // SwpmLog::log_simple_debug($input, true); $event_json = json_decode( $input ); $type = $event_json->type; SwpmLog::log_simple_debug( sprintf( 'Stripe subscription webhook received: %s. Checking if we need to handle this webhook.', $type ), true ); if ( 'customer.subscription.deleted' === $type || 'charge.refunded' === $type ) { // Subscription expired or refunded event //SwpmLog::log_simple_debug( sprintf( 'Stripe Subscription Webhook %s received. Processing request...', $type ), true ); // Let's form minimal ipn_data array for swpm_handle_subsc_cancel_stand_alone $customer = $event_json->data->object->customer; $subscr_id = $event_json->data->object->id; $ipn_data = array(); $ipn_data['subscr_id'] = $subscr_id; $ipn_data['parent_txn_id'] = $customer; swpm_handle_subsc_cancel_stand_alone( $ipn_data ); } if ( $type == 'customer.subscription.updated' ) { // Subscription updated webhook // Check that the status is "active" or "trialing". That way we don't process the webhook for "canceled" or "past_due" status. $status = isset($event_json->data->object->status)? $event_json->data->object->status : ''; SwpmLog::log_simple_debug( 'Stripe customer.subscription.updated webhook status: ' . $status, true ); if( $status != 'active' && $status != 'trialing' ) { SwpmLog::log_simple_debug( 'Stripe customer.subscription.updated webhook status is not "active" or "trialing". Ignoring this webhook.', true ); http_response_code( 200 ); // Tells Stripe we received this notification return; } // Let's form minimal ipn_data array $customer = $event_json->data->object->customer; $subscr_id = $event_json->data->object->id; $ipn_data = array(); $ipn_data['subscr_id'] = $subscr_id; $ipn_data['parent_txn_id'] = $customer; swpm_update_member_subscription_start_date_if_applicable( $ipn_data ); } if ( $type === 'invoice.payment_succeeded' ) { $billing_reason = isset( $event_json->data->object->billing_reason ) ? $event_json->data->object->billing_reason : ''; if ( $billing_reason == 'subscription_cycle' ) { //This is recurring/subscription payment invoice SwpmLog::log_simple_debug( sprintf( 'Stripe invoice.payment_succeeded webhook for subscription_cycle. This is a successful subscription charge. Capturing payment data.' ), true ); $sub_id = $event_json->data->object->subscription; //$cust_id = $event_json->data->object->billing_reason; //$date = $event_json->data->object->date; $price_in_cents = $event_json->data->object->amount_paid; //amount in cents $currency_code = $event_json->data->object->currency; $zero_cents = unserialize( SIMPLE_WP_MEMBERSHIP_STRIPE_ZERO_CENTS ); if ( in_array( $currency_code, $zero_cents, true ) ) { $payment_amount = $price_in_cents; } else { $payment_amount = $price_in_cents / 100;// The amount (in cents). This value is used in Stripe API. } $payment_amount = floatval( $payment_amount ); // Let's try to get first_name and last_name from full name $full_name = $event_json->data->object->customer_name; $name_pieces = explode( ' ', $full_name, 2 ); $first_name = $name_pieces[0]; if ( ! empty( $name_pieces[1] ) ) { $last_name = $name_pieces[1]; } //Retrieve the member record for this subscription $member_record = SwpmMemberUtils::get_user_by_subsriber_id( $sub_id ); if ( $member_record ) { // Found a member record $member_id = $member_record->member_id; $membership_level_id = $member_record->membership_level; if ( empty( $first_name ) ) { $first_name = $member_record->first_name; } if ( empty( $last_name ) ) { $last_name = $member_record->last_name; } } else { SwpmLog::log_simple_debug( 'Could not find an existing member record for the given subscriber ID: ' . $sub_id . '. This user profile may have been deleted.', false ); $member_id = ''; $membership_level_id = ''; } //Create the custom field $custom_field_value = 'subsc_ref=' . $membership_level_id; $custom_field_value .= '&swpm_id=' . $member_id; // Create the $ipn_data array. $ipn_data = array(); $ipn_data['mc_gross'] = $payment_amount; $ipn_data['first_name'] = $first_name; $ipn_data['last_name'] = $last_name; $ipn_data['payer_email'] = $event_json->data->object->customer_email; $ipn_data['membership_level'] = $membership_level_id; $ipn_data['txn_id'] = $event_json->data->object->charge; $ipn_data['subscr_id'] = $sub_id; $ipn_data['swpm_id'] = $member_id; $ipn_data['ip'] = ''; $ipn_data['custom'] = $custom_field_value; $ipn_data['gateway'] = 'stripe-sca-subs'; $ipn_data['status'] = 'subscription'; //TODO - Maybe handle the user access start date updating here (instead of "customer.subscription.updated" hook). //swpm_update_member_subscription_start_date_if_applicable( $ipn_data ); // Save the transaction record SwpmTransactions::save_txn_record( $ipn_data ); SwpmLog::log_simple_debug( 'Transaction data saved for Stripe subscription notification.', true ); } } //End of the webhook notification execution. //Give 200 status then exit out. SwpmLog::log_simple_debug( 'End of Stripe subscription webhook processing. Webhook type: ' . $type, true); http_response_code( 200 ); // Tells Stripe we received this notification return; } //The following will get executed only for DIRECT post (not webhooks). So it is executed at the time of payment in the browser (via HTTP POST). When the "hook" query arg is not set. SwpmLog::log_simple_debug( 'Stripe subscription IPN received. Processing request...', true ); // SwpmLog::log_simple_debug(print_r($_REQUEST, true), true);//Useful for debugging purpose // Include the Stripe library. SwpmMiscUtils::load_stripe_lib(); // Read and sanitize the request parameters. $button_id = sanitize_text_field( $_REQUEST['item_number'] ); $button_id = absint( $button_id ); $button_title = sanitize_text_field( $_REQUEST['item_name'] ); $stripe_token = isset( $_POST['stripeToken'] ) ? sanitize_text_field( stripslashes ( $_POST['stripeToken'] ) ) : ''; $stripe_token_type = isset( $_POST['stripeTokenType'] ) ? sanitize_text_field( stripslashes ( $_POST['stripeTokenType'] ) ) : ''; $stripe_email = filter_input( INPUT_POST, 'stripeEmail', FILTER_SANITIZE_EMAIL ); // Retrieve the CPT for this button $button_cpt = get_post( $button_id ); if ( ! $button_cpt ) { // Fatal error. Could not find this payment button post object. SwpmLog::log_simple_debug( 'Fatal Error! Failed to retrieve the payment button post object for the given button ID: ' . $button_id, false ); wp_die( esc_html( sprintf( 'Fatal Error! Payment button (ID: %d) does not exist. This request will fail.', $button_id ) ) ); } $plan_id = get_post_meta( $button_id, 'stripe_plan_id', true ); $descr = 'Subscription to "' . $plan_id . '" plan'; $membership_level_id = get_post_meta( $button_id, 'membership_level_id', true ); // Validate and verify some of the main values. // Validation passed. Go ahead with the charge. // Sandbox and other settings $settings = SwpmSettings::get_instance(); $sandbox_enabled = $settings->get_value( 'enable-sandbox-testing' ); //API keys $api_keys = SwpmMiscUtils::get_stripe_api_keys_from_payment_button( $button_id, ! $sandbox_enabled ); // Set secret API key in the Stripe library \Stripe\Stripe::setApiKey( $api_keys['secret'] ); // Get the credit card details submitted by the form $token = $stripe_token; // Create the charge on Stripe's servers - this will charge the user's card try { $customer = \Stripe\Customer::create( array( 'description' => $descr, 'email' => $stripe_email, 'source' => $token, 'plan' => $plan_id, 'trial_from_plan' => 'true', ) ); } catch ( Exception $e ) { SwpmLog::log_simple_debug( 'Error occurred during Stripe Subscribe. ' . $e->getMessage(), false ); $body = $e->getJsonBody(); $error = $body['error']; $error_string = wp_json_encode( $error ); SwpmLog::log_simple_debug( 'Error details: ' . $error_string, false ); wp_die( esc_html( 'Stripe subscription Error! ' . $e->getMessage() . $error_string ) ); } // Everything went ahead smoothly with the charge. SwpmLog::log_simple_debug( 'Stripe subscription successful.', true ); // let's add button_id to metadata $customer->metadata = array( 'button_id' => $button_id ); try { $customer->save(); } catch ( Exception $e ) { SwpmLog::log_simple_debug( 'Error occurred during Stripe customer metadata update. ' . $e->getMessage(), false ); $body = $e->getJsonBody(); SwpmLog::log_simple_debug( 'Error details: ' . $error_string, false ); } // Grab customer ID and set it as the transaction ID. $txn_id = $customer->id; // $charge->balance_transaction; // Grab subscription ID $subscr_id = $customer->subscriptions->data[0]->id; $custom = sanitize_text_field( $_REQUEST['custom'] ); $custom_var = SwpmTransactions::parse_custom_var( $custom ); $swpm_id = isset( $custom_var['swpm_id'] ) ? $custom_var['swpm_id'] : ''; $payment_amount = $customer->subscriptions->data[0]->plan->amount / 100; // Create the $ipn_data array. $ipn_data = array(); $ipn_data['mc_gross'] = $payment_amount; $ipn_data['first_name'] = ''; $ipn_data['last_name'] = ''; $ipn_data['payer_email'] = $stripe_email; $ipn_data['membership_level'] = $membership_level_id; $ipn_data['txn_id'] = $txn_id; $ipn_data['subscr_id'] = $subscr_id; $ipn_data['swpm_id'] = $swpm_id; $ipn_data['ip'] = $custom_var['user_ip']; $ipn_data['custom'] = $custom; $ipn_data['gateway'] = 'stripe'; $ipn_data['status'] = 'completed'; $ipn_data['address_street'] = ''; $ipn_data['address_city'] = ''; $ipn_data['address_state'] = ''; $ipn_data['address_zipcode'] = ''; $ipn_data['country'] = ''; $ipn_data['payment_button_id'] = $button_id; $ipn_data['is_live'] = ! $sandbox_enabled; // Handle the membership signup related tasks. swpm_handle_subsc_signup_stand_alone( $ipn_data, $membership_level_id, $txn_id, $swpm_id ); // Save the transaction record SwpmTransactions::save_txn_record( $ipn_data ); SwpmLog::log_simple_debug( 'Transaction data saved.', true ); // Trigger the stripe IPN processed action hook (so other plugins can can listen for this event). do_action( 'swpm_stripe_ipn_processed', $ipn_data ); do_action( 'swpm_payment_ipn_processed', $ipn_data ); // Redirect the user to the return URL (or to the homepage if a return URL is not specified for this payment button). $return_url = get_post_meta( $button_id, 'return_url', true ); if ( empty( $return_url ) ) { $return_url = SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL; } SwpmLog::log_simple_debug( 'Redirecting customer to: ' . $return_url, true ); SwpmLog::log_simple_debug( 'End of Stripe subscription IPN processing.', true, true ); SwpmMiscUtils::redirect_to_url( $return_url ); } } $swpm_stripe_subscription_ipn = new SwpmStripeSubscriptionIpnHandler(); PK!P@@swpm_handle_pp_ipn.phpnu[paypal_url = 'https://www.paypal.com/cgi-bin/webscr'; $this->ipn_log_file = 'ipn_handle_debug_swpm.log'; $this->ipn_response = ''; } public function swpm_validate_and_create_membership() { // Check Product Name , Price , Currency , Receivers email , $error_msg = ''; // Read the IPN and validate $gross_total = $this->ipn_data['mc_gross']; $transaction_type = $this->ipn_data['txn_type']; $txn_id = $this->ipn_data['txn_id']; $payment_status = $this->ipn_data['payment_status']; // Check payment status if ( ! empty( $payment_status ) ) { if ( 'Denied' == $payment_status ) { $this->debug_log( 'Payment status for this transaction is DENIED. You denied the transaction... most likely a cancellation of an eCheque. Nothing to do here.', false ); return false; } if ( 'Canceled_Reversal' === $payment_status ) { $this->debug_log( 'This is a dispute closed notification in your favour. The plugin will not do anyting.', false ); return true; } if ( 'Completed' !== $payment_status && 'Processed' !== $payment_status && 'Refunded' !== $payment_status && 'Reversed' !== $payment_status ) { $error_msg .= 'Funds have not been cleared yet. Transaction will be processed when the funds clear!'; $this->debug_log( $error_msg, false ); return false; } } // Check txn type if ( 'new_case' === $transaction_type ) { $this->debug_log( 'This is a dispute case. Nothing to do here.', true ); return true; } $custom = urldecode( $this->ipn_data['custom'] ); $this->ipn_data['custom'] = $custom; $customvariables = SwpmTransactions::parse_custom_var( $custom ); // Handle refunds if ( $gross_total < 0 ) { // This is a refund or reversal $this->debug_log( 'This is a refund notification. Refund amount: ' . $gross_total, true ); swpm_handle_subsc_cancel_stand_alone( $this->ipn_data, true ); return true; } if ( isset( $this->ipn_data['reason_code'] ) && 'refund' === $this->ipn_data['reason_code'] ) { $this->debug_log( 'This is a refund notification. Refund amount: ' . $gross_total, true ); swpm_handle_subsc_cancel_stand_alone( $this->ipn_data, true ); return true; } if ( ( 'subscr_signup' === $transaction_type ) ) { $this->debug_log( 'Subscription signup IPN received... (handled by the subscription IPN handler)', true ); // Code to handle the signup IPN for subscription $subsc_ref = $customvariables['subsc_ref']; if ( ! empty( $subsc_ref ) ) { $this->debug_log( 'Found a membership level ID. Creating member account...', true ); $swpm_id = $customvariables['swpm_id']; swpm_handle_subsc_signup_stand_alone( $this->ipn_data, $subsc_ref, $this->ipn_data['subscr_id'], $swpm_id ); // Handle customized subscription signup } return true; } elseif ( ( $transaction_type == 'subscr_cancel' ) || ( $transaction_type == 'subscr_eot' ) || ( $transaction_type == 'subscr_failed' ) ) { // Code to handle the IPN for subscription cancellation $this->debug_log( 'Subscription cancellation IPN received... (handled by the subscription IPN handler)', true ); swpm_handle_subsc_cancel_stand_alone( $this->ipn_data ); return true; } else { $cart_items = array(); $this->debug_log( 'Transaction Type: Buy Now/Subscribe', true ); $item_number = $this->ipn_data['item_number']; $item_name = $this->ipn_data['item_name']; $quantity = $this->ipn_data['quantity']; $mc_gross = $this->ipn_data['mc_gross']; $mc_currency = $this->ipn_data['mc_currency']; $current_item = array( 'item_number' => $item_number, 'item_name' => $item_name, 'quantity' => $quantity, 'mc_gross' => $mc_gross, 'mc_currency' => $mc_currency, ); array_push( $cart_items, $current_item ); } /*** Duplicate IPN check ***/ // Query the DB to check if we have already processed this transaction or not $txn_row = SwpmTransactions::get_transaction_row_by_txn_id($txn_id); // And if we have already processed it, do nothing and return true if (!empty($txn_row)) { $this->debug_log( "This transaction has already been processed (".$txn_id."). Nothing to do here.", true ); return true; } /*** End of duplicate IPN check ***/ $counter = 0; foreach ( $cart_items as $current_cart_item ) { $cart_item_data_num = $current_cart_item['item_number']; $cart_item_data_name = trim( $current_cart_item['item_name'] ); $cart_item_data_quantity = $current_cart_item['quantity']; $cart_item_data_total = $current_cart_item['mc_gross']; $cart_item_data_currency = $current_cart_item['mc_currency']; if ( empty( $cart_item_data_quantity ) ) { $cart_item_data_quantity = 1; } $this->debug_log( 'Item Number: ' . $cart_item_data_num, true ); $this->debug_log( 'Item Name: ' . $cart_item_data_name, true ); $this->debug_log( 'Item Quantity: ' . $cart_item_data_quantity, true ); $this->debug_log( 'Item Total: ' . $cart_item_data_total, true ); $this->debug_log( 'Item Currency: ' . $cart_item_data_currency, true ); // Get the button id $pp_hosted_button = false; $button_id = $cart_item_data_num;// Button id is the item number. $membership_level_id = get_post_meta( $button_id, 'membership_level_id', true ); if ( ! SwpmUtils::membership_level_id_exists( $membership_level_id ) ) { $this->debug_log( 'This payment button was not created in the plugin. This is a paypal hosted button.', true ); $pp_hosted_button = true; } // Price check $check_price = true; $msg = ''; $msg = apply_filters( 'swpm_before_price_check_filter', $msg, $current_cart_item ); if ( ! empty( $msg ) && $msg == 'price-check-override' ) {// This filter allows an extension to do a customized version of price check (if needed) $check_price = false; $this->debug_log( 'Price and currency check has been overridden by an addon/extension.', true ); } if ( $check_price && ! $pp_hosted_button ) { // Check according to buy now payment or subscription payment. $button_type = get_post_meta( $button_id, 'button_type', true ); if ( $button_type == 'pp_buy_now' ) {// This is a PayPal buy now type button $expected_amount = ( get_post_meta( $button_id, 'payment_amount', true ) ) * $cart_item_data_quantity; $expected_amount = round( $expected_amount, 2 ); $expected_amount = apply_filters( 'swpm_payment_amount_filter', $expected_amount, $button_id ); $received_amount = $cart_item_data_total; if ( $received_amount < $expected_amount ) { // Error! amount received is less than expected. This is invalid. $this->debug_log( 'Expected amount: ' . $expected_amount, true ); $this->debug_log( 'Received amount: ' . $received_amount, true ); $this->debug_log( 'Price check failed. Amount received is less than the amount expected. This payment will not be processed.', false ); return false; } } elseif ( $button_type == 'pp_subscription' ) {// This is a PayPal subscription type button //This is a "subscr_payment" type payment notification. The "subscr_signup" type gets handled before. $trial_billing_cycle = get_post_meta( $button_id, 'trial_billing_cycle', true ); $trial_billing_amount = get_post_meta( $button_id, 'trial_billing_amount', true ); $billing_amount = get_post_meta( $button_id, 'billing_amount', true ); if ( empty( $trial_billing_cycle ) ){ //No trial billing. Check main billing amount. Only need to check "mc_gross" which should cointain the "amount3" value. $this->debug_log( 'Trial billing is not enabled for this button.', true ); $expected_amount = round( $billing_amount, 2 ); } else { //Trial billing is specified for this button $this->debug_log( 'Trial billing is enabled for this button.', true ); if ( swpm_is_paypal_recurring_payment($this->ipn_data) ){ //This is a recurring payment of a subscription. $expected_amount = round( $billing_amount, 2 ); } else { //This is a trial payment of a subscription $expected_amount = round( $trial_billing_amount, 2 ); } } $received_amount = $cart_item_data_total; if ( $received_amount < $expected_amount ) { // Error! amount received is less than expected. This is invalid. $this->debug_log( 'Expected amount: ' . $expected_amount, true ); $this->debug_log( 'Received amount: ' . $received_amount, true ); $this->debug_log( 'Price check failed. Amount received is less than the amount expected. This payment will not be processed.', false ); return false; } } else { $this->debug_log( 'Error! Unexpected button type: ' . $button_type, false ); return false; } } // *** Handle Membership Payment *** // -------------------------------------------------------------------------------------- // ========= Need to find the (level ID) in the custom variable ============ $subsc_ref = $customvariables['subsc_ref'];// Membership level ID $this->debug_log( 'Membership payment paid for membership level ID: ' . $subsc_ref, true ); if ( ! empty( $subsc_ref ) ) { $swpm_id = ''; if ( isset( $customvariables['swpm_id'] ) ) { $swpm_id = $customvariables['swpm_id']; } if ( $transaction_type == 'web_accept' ) { $this->debug_log( 'Transaction type: web_accept. Creating member account...', true ); swpm_handle_subsc_signup_stand_alone( $this->ipn_data, $subsc_ref, $this->ipn_data['txn_id'], $swpm_id ); } elseif ( $transaction_type == 'subscr_payment' ) { $this->debug_log( 'Transaction type: subscr_payment. Checking if the member profile needed to be updated', true ); swpm_update_member_subscription_start_date_if_applicable( $this->ipn_data ); } } else { $this->debug_log( 'Membership level ID is missing in the payment notification! Cannot process this notification.', false ); } // == End of Membership payment handling == $counter++; } /*** Do Post payment operation and cleanup */ // Save the transaction data $this->debug_log( 'Saving transaction data to the database table.', true ); $this->ipn_data['gateway'] = 'paypal'; $this->ipn_data['payment_button_id'] = $button_id; $this->ipn_data['status'] = $this->ipn_data['payment_status']; // If the value ipn_data['ip'] is empty, try to detect the customer IP address using the variable custom['user_ip'] if (empty($this->ipn_data['ip']) && filter_var($customvariables['user_ip'], FILTER_VALIDATE_IP)) { $this->ipn_data['ip'] = $customvariables['user_ip']; } SwpmTransactions::save_txn_record( $this->ipn_data, $cart_items ); $this->debug_log( 'Transaction data saved.', true ); // Trigger the PayPal IPN processed action hook (so other plugins can can listen for this event). do_action( 'swpm_paypal_ipn_processed', $this->ipn_data ); do_action( 'swpm_payment_ipn_processed', $this->ipn_data ); return true; } public function swpm_validate_ipn() { // Generate the post string from the _POST vars aswell as load the _POST vars into an arry $post_string = ''; foreach ( $_POST as $field => $value ) { $this->ipn_data[ "$field" ] = $value; $post_string .= $field . '=' . urlencode( stripslashes( $value ) ) . '&'; } $this->post_string = $post_string; $this->debug_log( 'Post string : ' . $this->post_string, true ); // IPN validation check if ( $this->validate_ipn_using_remote_post() ) { // We can also use an alternative validation using the validate_ipn_using_curl() function return true; } else { return false; } } public function validate_ipn_using_remote_post() { $this->debug_log( 'Checking if PayPal IPN response is valid', true ); // Get received values from post data $validate_ipn = array( 'cmd' => '_notify-validate' ); $validate_ipn += wp_unslash( $_POST ); // Send back post vars to paypal $params = array( 'body' => $validate_ipn, 'timeout' => 60, 'httpversion' => '1.1', 'compress' => false, 'decompress' => false, 'user-agent' => 'Simple Membership Plugin', ); // Post back to get a response. $connection_url = $this->sandbox_mode ? 'https://www.sandbox.paypal.com/cgi-bin/webscr' : 'https://www.paypal.com/cgi-bin/webscr'; $this->debug_log( 'Connecting to: ' . $connection_url, true ); $response = wp_safe_remote_post( $connection_url, $params ); // The following two lines can be used for debugging // $this->debug_log( 'IPN Request: ' . print_r( $params, true ) , true); // $this->debug_log( 'IPN Response: ' . print_r( $response, true ), true); // Check to see if the request was valid. if ( ! is_wp_error( $response ) && strstr( $response['body'], 'VERIFIED' ) ) { $this->debug_log( 'IPN successfully verified.', true ); return true; } // Invalid IPN transaction. Check the log for details. $this->debug_log( 'IPN validation failed.', false ); if ( is_wp_error( $response ) ) { $this->debug_log( 'Error response: ' . $response->get_error_message(), false ); } return false; } public function debug_log( $message, $success, $end = false ) { SwpmLog::log_simple_debug( $message, $success, $end ); } } // Start of IPN handling (script execution) $ipn_handler_instance = new swpm_paypal_ipn_handler(); $settings = SwpmSettings::get_instance(); $debug_enabled = $settings->get_value( 'enable-debug' ); if ( ! empty( $debug_enabled ) ) { $debug_log = 'log.txt'; // Debug log file name echo esc_html( sprintf( 'Debug logging is enabled. Check the %s file for debug output.', $debug_log ) ); $ipn_handler_instance->ipn_log = true; $ipn_handler_instance->ipn_log_file = $debug_log; if ( empty( $_POST ) ) { $ipn_handler_instance->debug_log( 'This debug line was generated because you entered the URL of the ipn handling script in the browser.', true, true ); exit; } } $sandbox_enabled = $settings->get_value( 'enable-sandbox-testing' ); if ( ! empty( $sandbox_enabled ) ) { $ipn_handler_instance->paypal_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr'; $ipn_handler_instance->sandbox_mode = true; } $ipn_handler_instance->debug_log( 'Paypal Class Initiated by ' . $_SERVER['REMOTE_ADDR'], true ); // Validate the IPN if ( $ipn_handler_instance->swpm_validate_ipn() ) { $ipn_handler_instance->debug_log( 'Creating product Information to send.', true ); if ( ! $ipn_handler_instance->swpm_validate_and_create_membership() ) { $ipn_handler_instance->debug_log( 'IPN product validation failed.', false ); } } $ipn_handler_instance->debug_log( 'Paypal class finished.', true, true ); PK!4zswpm-braintree-buy-now-ipn.phpnu[handle_braintree_ipn(); } public function handle_braintree_ipn() { SwpmLog::log_simple_debug("Braintree Buy Now IPN received. Processing request...", true); //SwpmLog::log_simple_debug(print_r($_REQUEST, true), true);//Useful for debugging purpose //Include the Braintree library. require_once(SIMPLE_WP_MEMBERSHIP_PATH . 'lib/braintree/lib/autoload.php'); //Read and sanitize the request parameters. $button_id = filter_input(INPUT_POST, 'item_number', FILTER_SANITIZE_NUMBER_INT); $button_title = sanitize_text_field($_POST['item_name']); $payment_amount = sanitize_text_field($_POST['item_price']); //Retrieve the CPT for this button $button_cpt = get_post($button_id); if (!$button_cpt) { //Fatal error. Could not find this payment button post object. SwpmLog::log_simple_debug("Fatal Error! Failed to retrieve the payment button post object for the given button ID: " . $button_id, false); wp_die("Fatal Error! Payment button (ID: " . $button_id . ") does not exist. This request will fail."); } $membership_level_id = get_post_meta($button_id, 'membership_level_id', true); //Validate and verify some of the main values. $true_payment_amount = get_post_meta($button_id, 'payment_amount', true); $true_payment_amount = apply_filters('swpm_payment_amount_filter',$true_payment_amount,$button_id); if ($payment_amount != $true_payment_amount) { //Fatal error. Payment amount may have been tampered with. $error_msg = 'Fatal Error! Received payment amount (' . $payment_amount . ') does not match with the original amount (' . $true_payment_amount . ')'; SwpmLog::log_simple_debug($error_msg, false); wp_die($error_msg); } //Validation passed. Go ahead with the charge. //Sandbox and other settings $settings = SwpmSettings::get_instance(); $sandbox_enabled = $settings->get_value('enable-sandbox-testing'); if ($sandbox_enabled) { SwpmLog::log_simple_debug("Sandbox payment mode is enabled. Using sandbox enviroment.", true); $braintree_env = "sandbox"; //Use sandbox environment } else { $braintree_env = "production"; //Use production environment } //Set Braintree library environment and keys try { Braintree_Configuration::environment($braintree_env); Braintree_Configuration::merchantId(get_post_meta($button_id, 'braintree_merchant_acc_id', true)); Braintree_Configuration::publicKey(get_post_meta($button_id, 'braintree_public_key', true)); Braintree_Configuration::privateKey(get_post_meta($button_id, 'braintree_private_key', true)); $braintree_merc_acc_name = get_post_meta($button_id, 'braintree_merchant_acc_name', true); // Create the charge on Braintree's servers - this will charge the user's card $nonce = sanitize_text_field($_POST['payment_method_nonce']); $first_name= sanitize_text_field( $_POST['first_name'] ); $last_name= sanitize_text_field( $_POST['last_name'] ); $email= sanitize_text_field( $_POST['member_email'] ); $customer = array( 'firstName' => $first_name, 'lastName' => $last_name, 'email' => $email ); $result = Braintree_Transaction::sale([ 'amount' => $payment_amount, 'paymentMethodNonce' => $nonce, 'channel' => 'TipsandTricks_SP', 'customer' => $customer, 'options' => [ 'submitForSettlement' => True ], 'merchantAccountId' => $braintree_merc_acc_name, ]); } catch (Exception $e) { SwpmLog::log_simple_debug("Braintree library error occurred: " . get_class($e) . ", button ID: " . $button_id, false); wp_die('Braintree library error occurred: ' . get_class($e)); } if (!$result->success) { SwpmLog::log_simple_debug("Braintree transaction error occurred: " . $result->transaction->status . ", message: ".$result->message." , button ID: " . $button_id, false); wp_die("Braintree transaction error occurred: " . $result->transaction->status); } else { //Everything went ahead smoothly with the charge. SwpmLog::log_simple_debug("Braintree Buy Now charge successful.", true); //Grab the transaction ID. $txn_id = $result->transaction->id; //$charge->balance_transaction; $custom = sanitize_text_field($_POST['custom']); $custom_var = SwpmTransactions::parse_custom_var($custom); $swpm_id = isset($custom_var['swpm_id']) ? $custom_var['swpm_id'] : ''; //Create the $ipn_data array. $ipn_data = array(); $ipn_data['mc_gross'] = $payment_amount; $ipn_data['first_name'] = sanitize_text_field($_POST['first_name']); $ipn_data['last_name'] = sanitize_text_field($_POST['last_name']); $ipn_data['payer_email'] = filter_input(INPUT_POST, 'member_email', FILTER_SANITIZE_EMAIL); $ipn_data['membership_level'] = $membership_level_id; $ipn_data['txn_id'] = $txn_id; $ipn_data['subscr_id'] = $txn_id; $ipn_data['swpm_id'] = $swpm_id; $ipn_data['ip'] = $custom_var['user_ip']; $ipn_data['custom'] = $custom; $ipn_data['gateway'] = 'braintree'; $ipn_data['status'] = 'completed'; $ipn_data['address_street'] = ''; $ipn_data['address_city'] = ''; $ipn_data['address_state'] = ''; $ipn_data['address_zipcode'] = ''; $ipn_data['country'] = ''; //Handle the membership signup related tasks. swpm_handle_subsc_signup_stand_alone($ipn_data, $membership_level_id, $txn_id, $swpm_id); //Save the transaction record SwpmTransactions::save_txn_record($ipn_data); SwpmLog::log_simple_debug('Transaction data saved.', true); //Trigger the stripe IPN processed action hook (so other plugins can can listen for this event). do_action('swpm_braintree_ipn_processed', $ipn_data); do_action('swpm_payment_ipn_processed', $ipn_data); //Redirect the user to the return URL (or to the homepage if a return URL is not specified for this payment button). $return_url = get_post_meta($button_id, 'return_url', true); if (empty($return_url)) { $return_url = SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL; } SwpmLog::log_simple_debug("Redirecting customer to: " . $return_url, true); SwpmLog::log_simple_debug("End of Braintree Buy Now IPN processing.", true, true); SwpmMiscUtils::redirect_to_url($return_url); } } } $swpm_braintree_buy_ipn = new SwpmBraintreeBuyNowIpnHandler(); PK! index.htmlnu[PK!ɗCCswpm-smart-checkout-ipn.phpnu[PK!fWnmUUCswpm_handle_subsc_ipn.phpnu[PK!MS##$ՙswpm-stripe-sca-subscription-ipn.phpnu[PK!?swpm-stripe-buy-now-ipn.phpnu[PK!j722swpm-stripe-sca-buy-now-ipn.phpnu[PK!m/!!+ swpm-stripe-sca-checkout-session-create.phpnu[PK!44 /swpm-stripe-subscription-ipn.phpnu[PK!P@@dswpm_handle_pp_ipn.phpnu[PK!4zswpm-braintree-buy-now-ipn.phpnu[PK! index.htmlnu[PK