How can I interact with a third party service that sends POST requests to me in response to the order information that I send them?

I'm developing a payment gateway for Wordpress. I should send the data to a third party and get the confirmation code (and other info) from them. The problem is that they don't return this confirmation code as a direct response to my POST request. Instead, they process it, and send a POST request to a URL that I can specify. Because of this, I can't use the wp_remote_post.

How can I implement this, and if possible, ensure that the user stays on my page?

Thanks.

Edit: They wanted me to edit this with code, so here it is:

?php
class SPYR_AuthorizeNet_AIM extends WC_Payment_Gateway {

// Setup our Gateway's id, description and other values
function __construct() {

    // The global ID for this Payment method
    $this-id = "spyr_authorizenet_aim";

    // The Title shown on the top of the Payment Gateways Page next to all the other Payment Gateways
    $this-method_title = __( "Authorize.net AIM", 'spyr-authorizenet-aim' );

    // The description for this Payment Gateway, shown on the actual Payment options page on the backend
    $this-method_description = __( "Authorize.net AIM Payment Gateway Plug-in for WooCommerce", 'spyr-authorizenet-aim' );

    // The title to be used for the vertical tabs that can be ordered top to bottom
    $this-title = __( "Authorize.net AIM", 'spyr-authorizenet-aim' );

    // If you want to show an image next to the gateway's name on the frontend, enter a URL to an image.
    $this-icon = null;

    // Bool. Can be set to true if you want payment fields to show on the checkout 
    // if doing a direct integration, which we are doing in this case
    $this-has_fields = true;

    // Supports the default credit card form
    $this-supports = array( 'default_credit_card_form' );

    // This basically defines your settings which are then loaded with init_settings()
    $this-init_form_fields();

    // After init_settings() is called, you can get the settings and load them into variables, e.g:
    // $this-title = $this-get_option( 'title' );
    $this-init_settings();

    // Turn these settings into variables we can use
    foreach ( $this-settings as $setting_key = $value ) {
        $this-$setting_key = $value;
    }

    // Lets check for SSL
    add_action( 'admin_notices', array( $this,  'do_ssl_check' ) );

    // Save settings
    if ( is_admin() ) {
        // Versions over 2.0
        // Save our administration options. Since we are not going to be doing anything special
        // we have not defined 'process_admin_options' in this class so the method in the parent
        // class will be used instead
        add_action( 'woocommerce_update_options_payment_gateways_' . $this-id, array( $this, 'process_admin_options' ) );
    }       
} // End __construct()

// Build the administration fields for this specific Gateway
public function init_form_fields() {
    $this-form_fields = array(
        'enabled' = array(
            'title'     = __( 'Enable / Disable', 'spyr-authorizenet-aim' ),
            'label'     = __( 'Enable this payment gateway', 'spyr-authorizenet-aim' ),
            'type'      = 'checkbox',
            'default'   = 'no',
        ),
        'title' = array(
            'title'     = __( 'Title', 'spyr-authorizenet-aim' ),
            'type'      = 'text',
            'desc_tip'  = __( 'Payment title the customer will see during the checkout process.', 'spyr-authorizenet-aim' ),
            'default'   = __( 'Credit card', 'spyr-authorizenet-aim' ),
        ),
        'description' = array(
            'title'     = __( 'Description', 'spyr-authorizenet-aim' ),
            'type'      = 'textarea',
            'desc_tip'  = __( 'Payment description the customer will see during the checkout process.', 'spyr-authorizenet-aim' ),
            'default'   = __( 'Pay securely using your credit card.', 'spyr-authorizenet-aim' ),
            'css'       = 'max-width:350px;'
        ),
        'api_login' = array(
            'title'     = __( 'Authorize.net API Login', 'spyr-authorizenet-aim' ),
            'type'      = 'text',
            'desc_tip'  = __( 'This is the API Login provided by Authorize.net when you signed up for an account.', 'spyr-authorizenet-aim' ),
        ),
        'trans_key' = array(
            'title'     = __( 'Authorize.net Transaction Key', 'spyr-authorizenet-aim' ),
            'type'      = 'password',
            'desc_tip'  = __( 'This is the Transaction Key provided by Authorize.net when you signed up for an account.', 'spyr-authorizenet-aim' ),
        ),
        'environment' = array(
            'title'     = __( 'Authorize.net Test Mode', 'spyr-authorizenet-aim' ),
            'label'     = __( 'Enable Test Mode', 'spyr-authorizenet-aim' ),
            'type'      = 'checkbox',
            'description' = __( 'Place the payment gateway in test mode.', 'spyr-authorizenet-aim' ),
            'default'   = 'no',
        )
    );      
}

// Submit payment and handle response
public function process_payment( $order_id ) {
    global $woocommerce;

    // Get this Order's information so that we know
    // who to charge and how much
    $customer_order = new WC_Order( $order_id );

    // Are we testing right now or is it a real transaction
    $environment = ( $this-environment == "yes" ) ? 'TRUE' : 'FALSE';

    // Decide which URL to post to
    $environment_url = ( "FALSE" == $environment ) 
                       ? 'https://secure.authorize.net/gateway/transact.dll'
                       : 'https://test.authorize.net/gateway/transact.dll';

    // This is where the fun stuff begins
    $payload = array(
        // Authorize.net Credentials and API Info
        "x_tran_key"            = $this-trans_key,
        "x_login"               = $this-api_login,
        "x_version"             = "3.1",

        // Order total
        "x_amount"              = $customer_order-order_total,

        // Credit Card Information
        "x_card_num"            = str_replace( array(' ', '-' ), '', $_POST['spyr_authorizenet_aim-card-number'] ),
        "x_card_code"           = ( isset( $_POST['spyr_authorizenet_aim-card-cvc'] ) ) ? $_POST['spyr_authorizenet_aim-card-cvc'] : '',
        "x_exp_date"            = str_replace( array( '/', ' '), '', $_POST['spyr_authorizenet_aim-card-expiry'] ),

        "x_type"                = 'AUTH_CAPTURE',
        "x_invoice_num"         = str_replace( "#", "", $customer_order-get_order_number() ),
        "x_test_request"        = $environment,
        "x_delim_char"          = '|',
        "x_encap_char"          = '',
        "x_delim_data"          = "TRUE",
        "x_relay_response"      = "FALSE",
        "x_method"              = "CC",

        // Billing Information
        "x_first_name"          = $customer_order-billing_first_name,
        "x_last_name"           = $customer_order-billing_last_name,
        "x_address"             = $customer_order-billing_address_1,
        "x_city"                = $customer_order-billing_city,
        "x_state"               = $customer_order-billing_state,
        "x_zip"                 = $customer_order-billing_postcode,
        "x_country"             = $customer_order-billing_country,
        "x_phone"               = $customer_order-billing_phone,
        "x_email"               = $customer_order-billing_email,

        // Shipping Information
        "x_ship_to_first_name"  = $customer_order-shipping_first_name,
        "x_ship_to_last_name"   = $customer_order-shipping_last_name,
        "x_ship_to_company"     = $customer_order-shipping_company,
        "x_ship_to_address"     = $customer_order-shipping_address_1,
        "x_ship_to_city"        = $customer_order-shipping_city,
        "x_ship_to_country"     = $customer_order-shipping_country,
        "x_ship_to_state"       = $customer_order-shipping_state,
        "x_ship_to_zip"         = $customer_order-shipping_postcode,

        // Some Customer Information
        "x_cust_id"             = $customer_order-user_id,
        "x_customer_ip"         = $_SERVER['REMOTE_ADDR'],

    );

    // Send this payload to Authorize.net for processing
    $response = wp_remote_post( $environment_url, array(
        'method'    = 'POST',
        'body'      = http_build_query( $payload ),
        'timeout'   = 90,
        'sslverify' = false,
    ) );

    if ( is_wp_error( $response ) ) 
        throw new Exception( __( 'We are currently experiencing problems trying to connect to this payment gateway. Sorry for the inconvenience.', 'spyr-authorizenet-aim' ) );

    if ( empty( $response['body'] ) )
        throw new Exception( __( 'Authorize.net\'s Response was empty.', 'spyr-authorizenet-aim' ) );

    // Retrieve the body's resopnse if no errors found
    $response_body = wp_remote_retrieve_body( $response );

    // Parse the response into something we can read
    foreach ( preg_split( "/\r?\n/", $response_body ) as $line ) {
        $resp = explode( "|", $line );
    }

    // Get the values we need
    $r['response_code']             = $resp[0];
    $r['response_sub_code']         = $resp[1];
    $r['response_reason_code']      = $resp[2];
    $r['response_reason_text']      = $resp[3];

    // Test the code to know if the transaction went through or not.
    // 1 or 4 means the transaction was a success
    if ( ( $r['response_code'] == 1 ) || ( $r['response_code'] == 4 ) ) {
        // Payment has been successful
        $customer_order-add_order_note( __( 'Authorize.net payment completed.', 'spyr-authorizenet-aim' ) );

        // Mark order as Paid
        $customer_order-payment_complete();

        // Empty the cart (Very important step)
        $woocommerce-cart-empty_cart();

        // Redirect to thank you page
        return array(
            'result'   = 'success',
            'redirect' = $this-get_return_url( $customer_order ),
        );
    } else {
        // Transaction was not succesful
        // Add notice to the cart
        wc_add_notice( $r['response_reason_text'], 'error' );
        // Add note to the order for your reference
        $customer_order-add_order_note( 'Error: '. $r['response_reason_text'] );
    }

}

// Validate fields
public function validate_fields() {
    return true;
}

// Check if we are forcing SSL on checkout pages
// Custom function not required by the Gateway
public function do_ssl_check() {
    if( $this-enabled == "yes" ) {
        if( get_option( 'woocommerce_force_ssl_checkout' ) == "no" ) {
            echo "div class=\"error\"p". sprintf( __( "strong%s/strong is enabled and WooCommerce is not forcing the SSL certificate on your checkout page. Please ensure that you have a valid SSL certificate and that you are a href=\"%s\"forcing the checkout pages to be secured./a" ), $this-method_title, admin_url( 'admin.php?page=wc-settingstab=checkout' ) ) ."/p/div";  
        }
    }       
}}

Topic wp-remote-request plugin-development Wordpress

Category Web


Since I doubt "pretty" URLs are important here, it's incredibly easy to create an endpoint/handler using the AJAX api (the other option is to use the rewrite API and listen out for a hit).

Don't be put off by the term "AJAX" - this is simply a script within your WordPress install that will fire a matching hook for whatever is passed as the action parameter (either via GET or POST).

For example, example.com/wp-admin/admin-ajax.php?action=gateway:

function wpse_225366_gateway_response() {
    // Do your thang

    exit;
}

// The wp_ajax_nopriv_{action} hook fires for non-authenticated users
add_action( 'wp_ajax_nopriv_gateway', 'wpse_225366_gateway_response' );

// The wp_ajax_{action} hook fires for authenticated users
add_action( 'wp_ajax_gateway',        'wpse_225366_gateway_response' );

You'll probably want to call the action something less generic than gateway, but I hope this illustrates how things work.

You can use the following to get the response URL programmatically:

$response_url = admin_url( 'admin-ajax.php?action=gateway' );

About

Geeks Mental is a community that publishes articles and tutorials about Web, Android, Data Science, new techniques and Linux security.