Remote site pending order to main site subscription

This is a useful way to provide custom purchase funnels on remote site(s) that fulfill on the main site, particularly for subscription management. Noting that Woo Subscriptions has some complex ways of processing coupons and to expect some finagling to get discounts to work.

Remote site code snippet. Include read/write Woo REST API keys to the main site:

add_action( 'woocommerce_thankyou', function( $order_id ) {

	// Settings
	$url = 'https://main-site.com/wp-json/wc-ccom/v1/orderin';
	$auth = 'ck_##########:cs_##########';

	// Assemble Payload
	$order = wc_get_order( $order_id );
	$data = (object) [
		'created_via' => $_SERVER['HTTP_HOST'],
		'order_number' => $order->get_id(),
		'order_number_formatted' => 'PREFIX-' . $order->get_id(),
		'billing_address_1' => $order->get_billing_address_1(),
		'billing_address_2' => $order->get_billing_address_2(),
		'billing_city' => $order->get_billing_city(),
		'billing_company' => $order->get_billing_company(),
		'billing_country' => $order->get_billing_country(),
		'billing_email' => $order->get_billing_email(),
		'billing_first_name' => $order->get_billing_first_name(),
		'billing_last_name' => $order->get_billing_last_name(),
		'billing_phone' => $order->get_billing_phone(),
		'billing_postcode' => $order->get_billing_postcode(),
		'billing_state' => $order->get_billing_state(),
		'shipping_address_1' => $order->get_shipping_address_1(),
		'shipping_address_2' => $order->get_shipping_address_2(),
		'shipping_city' => $order->get_shipping_city(),
		'shipping_company' => $order->get_shipping_company(),
		'shipping_country' => $order->get_shipping_country(),
		'shipping_first_name' => $order->get_shipping_first_name(),
		'shipping_last_name' => $order->get_shipping_last_name(),
		'shipping_postcode' => $order->get_shipping_postcode(),
		'shipping_state' => $order->get_shipping_state(),
		'line_items' => [],
	];

	// Add Line Items To Payload
	foreach( $order->get_items() as $item_id => $item ) {
		$data->line_items[] = (object) [
			'name' => $item->get_name(),
			'quantity' => $item->get_quantity(),
			'subtotal' => $item->get_subtotal(),
			'total' => $item->get_total(),
		];
	}

	// Transmit
	$args = [
		'headers' => [
			'Authorization' => 'Basic ' . $auth,
			'Accept' => 'application/json',
			'Content-Type' => 'application/json',
		],
		'body' => json_encode( $data ),
		'timeout' => 30,
	];
	$response = wp_remote_post( $url, $args );

	// Handle Success
	$response_body = json_decode( wp_remote_retrieve_body( $response ) );
	if( wp_remote_retrieve_response_code( $response ) === 200 ) {
		if( isset( $response_body->redirect_url ) ) {
			wp_redirect( $response_body->redirect_url );
			exit;
		}
	}

	// Handle Error
	if( isset( $response_body->error_message ) ) {
		printf( '<p><strong style="color:red;">%s</strong></p>', $response_body->error_message );
		return;
	}

} );

The following code snippets sit on the main site receiving the order:

Support custom order numbering:

add_filter( 'woocommerce_order_number', function( $order_id, $order ) {

	$order = wc_get_order( $order_id );
	if( ! $order ) {
		return $order_id;
	}
	return $order->get_meta( '_order_number_formatted' )
		? $order->get_meta( '_order_number_formatted' )
		: $order_id;

}, 10, 2 );

REST API order receiver:

// Settings
function wc_ccom_orderin_get_pid( $title ) {

	$product_ids = [
		'Product Title' => 123456,
	];

	return isset( $product_ids[$title] )
		? $product_ids[$title] : false;

}

// REST API Route
add_action( 'rest_api_init', function() {

	register_rest_route(
		'wc-ccom/v1',
		'/orderin',
		[
			'methods' => 'POST',
			'callback' => 'wc_ccom_orderin',
			'permission_callback' => current_user_can( 'manage_woocommerce' ),
		]
	);

} );

// REST API Function
function wc_ccom_orderin( $request ) {

	// Parameters
	$params = (object) $request->get_params();

	// Start Responder
	$response = new WP_REST_Response();

	// Log Request
	$logger = wc_get_logger();
	$logger->info( print_r( $params, true ), [ 'source' => 'wc_ccom_orderin' ] );

	// Check Preexistence
	$args = [
		'meta_key' => '_order_number',
		'meta_value' => $params->order_number,
		'meta_compare' => '=',
		'return' => 'ids',
	];
	$orders = wc_get_orders( $args );
	if( $orders ) {
		$response->set_status( 400 );
		$response->set_data( (object) [
			'error_message' => sprintf(
				'Order number %d preexists as %d',
				$params->order_number,
				$orders[0]
			),
		] );
		return $response;
	}

	// Get Customer ID And Login
	$user = get_user_by( 'email', $params->billing_email );
	if( $user ) {
		$user_id = $user->ID;
	} else {
		$user_id = wp_create_user(
			$params->billing_email,
			wp_generate_password(),
			$params->billing_email
		);
	}

	// Billing Address
	$address_billing = [
		'address_1' => $params->billing_address_1,
		'address_2' => $params->billing_address_2,
		'city' => $params->billing_city,
		'company' => $params->billing_company,
		'country' => $params->billing_country,
		'email' => $params->billing_email,
		'first_name' => $params->billing_first_name,
		'last_name' => $params->billing_last_name,
		'phone' => $params->billing_phone,
		'postcode' => $params->billing_postcode,
		'state' => $params->billing_state,
	];

	// Shipping Address
	$address_shipping = [
		'address_1' => $params->shipping_address_1,
		'address_2' => $params->shipping_address_2,
		'city' => $params->shipping_city,
		'company' => $params->shipping_company,
		'country' => $params->shipping_country,
		'first_name' => $params->shipping_first_name,
		'last_name' => $params->shipping_last_name,
		'postcode' => $params->shipping_postcode,
		'state' => $params->shipping_state,
	];

	// Create Pending Order For Customer
	$order = wc_create_order();
	$order->set_customer_user_agent( $params->created_via );
	$order->set_status( 'pending' );
	$order->set_customer_id( $user_id );
	$order->update_meta_data( '_order_number', $params->order_number );
	$order->update_meta_data( '_order_number_formatted', $params->order_number_formatted );
	$order->set_address( $address_billing, 'billing' );
	$order->set_address( $address_shipping, 'shipping' );

	// Loop Line Items
	$subtotal = 0;
	foreach( $params->line_items as $item ) {

		$item = (object) $item;

		// Map Product To This Site
		$product_id = wc_ccom_orderin_get_pid( $item->name );
		$product = $product_id ? wc_get_product( $product_id ) : false;

		// Handle Errors
		if( ! $product_id || ! $product ) {
			$response->set_status( 400 );
			$response->set_data( (object) [
				'error_message' => 'Unable to match product by title: '
					. $item->name
			] );
			return $response;
		}

		// Create Subscription
		$args = [
			'customer_id' => $user_id,
			'order_id' => $order->get_id(),
			'status' => 'pending',
			'billing_period' => WC_Subscriptions_Product::get_period( $product ),
			'billing_interval' => WC_Subscriptions_Product::get_interval( $product )
		];
		$subscription = wcs_create_subscription( $args );

		// Handle Errors
		if( is_wp_error( $subscription ) ) {
			$response->set_status( 400 );
			$response->set_data( (object) [ 'error_message' => $subscription->get_error_message() ] );
			return $response;
		}

		// Set Addresses
		$subscription->set_address( $address_billing, 'billing' );
		$subscription->set_address( $address_shipping, 'shipping' );

		// Set Totals
		// Adding Prices Here Isn't Obeyed By Subscriptions Order Pay Page
		$subtotal += (float) $item->total;
		$order->add_product( $product, $item->quantity, [ 'total' => $item->total ] );
		$subscription->add_product( $product, $item->quantity );

		// Set Discount (Negative Fee) For Ongoing Discounts
		/*
			$differential = (float) -1 * ( $product->get_price() - $item->subtotal );
			if( $differential ) {
				$fee = new WC_Order_Item_Fee();
				$fee->set_name( 'Discount on ' . $item->name );
				$fee->set_amount( $differential );
				$fee->set_tax_status( 'taxable' );
				$fee->set_total( $differential );
				$order->add_item( $fee );
				$subscription->add_item( $fee );
			}
		*/

		// Set Dates
		$start_date = gmdate( 'Y-m-d H:i:s' );
		$dates = array(
			'trial_end' => WC_Subscriptions_Product::get_trial_expiration_date(
				$product, $start_date
			),
			'next_payment' => WC_Subscriptions_Product::get_first_renewal_payment_date(
				$product, $start_date
			),
			'end' => WC_Subscriptions_Product::get_expiration_date(
				$product, $start_date
			),
		);
		$subscription->update_dates( $dates );
		$subscription->calculate_totals();
		$subscription->save();

	}

	// Set Parent Order Coupon (One-Time Or Ongoing)
	$coupon_code = '';
	if( $coupon_code ) {
		$order->apply_coupon( $coupon_code );
	}

	// Save Parent Order
	$order->calculate_totals();
	$order->save();

	// Order Note
	$order->add_order_note( 'This order was created by remote site API call.' );

	// Log Them In
	wp_set_current_user( $user_id, $user_login );
	wp_set_auth_cookie( $user_id );
	//do_action( 'wp_login', $user_login );

	// API Reply
	$response->set_status( 200 );
	$response->set_data( (object) [ 'redirect_url' => $order->get_checkout_payment_url() ] );
	return $response;

}

Send order complete signal to remote site upon parent order paid. Include read/write Woo REST API keys to the main site:

add_action( 'woocommerce_thankyou', function( $order_id ) {

	// Settings
	$url = 'https://remote-site.com/wp-json/wc/v3/orders/';
	$auth = 'ck_##########:cs_##########';

	// Get Order Source ID
	$order = wc_get_order( $order_id );
	$order_number = $order->get_meta( '_order_number', true );
	$order_number_formatted = $order->get_meta( '_order_number_formatted', true );
	if( substr( $order_number_formatted, 0, 6 ) !== 'PREFIX' ) {
		return;
	}

	// Transmit
	$args = [
		'headers' => [
			'Authorization' => 'Basic ' . base64_encode( $auth ),
			'Accept' => 'application/json',
			'Content-Type' => 'application/json',
		],
		'body' => json_encode( [ 'status' => 'completed' ] ),
		'method' => 'PUT',
		'timeout' => 30,
	];
	$response = wp_remote_request( $url . $order_number, $args );

} );

Instructions for Remote site pending order to main site subscription

  1. Log into a staging or locally hosted clone of your site.
  2. Install and activate Code Snippets plugin.
  3. WP Admin > Snippets > Add New
  4. Copy and paste the code from the section above.
  5. Check to ensure formatting came over properly.
  6. Customize the code as desired.
  7. Add a meaningful title.
  8. Select whether to run on front-end or back-end (or both).
  9. Click “Save and Activate”.
  10. Test your site to ensure it works.
  11. Disable if any problems, or recover.
  12. Repeat for live environment.

Need help modifying Remote site pending order to main site subscription?

Contact me. I can help with fitting projects or refer to my partner.

License

All code snippets are licensed GPLv2 (or later) matching WordPress licensing.

Except when otherwise stated in writing the copyright holders and/or other parties provide the program as-is without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose.

Disclaimer of warranty