public function process_shipping_callback( WP_REST_Request $request ) {
$paypal_order_id = $request->get_param( 'id' );
$shipping_address = $request->get_param( 'shipping_address' );
$shipping_option = $request->get_param( 'shipping_option' );
$purchase_units = $request->get_param( 'purchase_units' );
// Note: shipping_option may or may not be present.
if ( empty( $paypal_order_id ) || empty( $shipping_address ) || empty( $purchase_units ) ) {
$response = $this->get_update_shipping_error_response();
return new WP_REST_Response( $response, 422 );
}
// Get the WC order.
$order = PayPalHelper::get_wc_order_from_paypal_custom_id( $purchase_units[0]['custom_id'] ?? '{}' );
if ( ! $order ) {
$custom_id = isset( $purchase_units[0]['custom_id'] ) ? $purchase_units[0]['custom_id'] : '{}';
WC_Gateway_Paypal::log( 'Unable to determine WooCommerce order from PayPal custom ID: ' . $custom_id );
$response = $this->get_update_shipping_error_response();
return new WP_REST_Response( $response, 422 );
}
// Compare PayPal order IDs.
$paypal_order_id_from_order_meta = $order->get_meta( '_paypal_order_id', true );
if ( empty( $paypal_order_id_from_order_meta ) || $paypal_order_id !== $paypal_order_id_from_order_meta ) {
WC_Gateway_Paypal::log(
'PayPal order ID mismatch. Order ID: ' . $order->get_id() .
'. PayPal order ID (request): ' . $paypal_order_id .
'. PayPal order ID (order meta): ' . $paypal_order_id_from_order_meta
);
$response = $this->get_update_shipping_error_response();
return new WP_REST_Response( $response, 422 );
}
// Validate that the order is in a valid state for shipping updates.
// Only draft or pending orders should accept shipping updates.
if ( ! in_array( $order->get_status(), array( OrderStatus::CHECKOUT_DRAFT, OrderStatus::PENDING ), true ) ) {
WC_Gateway_Paypal::log(
'Order is not in a valid state for shipping updates. Order ID: ' . $order->get_id() .
'. Order status: ' . $order->get_status()
);
$response = $this->get_update_shipping_error_response();
return new WP_REST_Response( $response, 422 );
}
// If the order has a PayPal transaction ID, a charge has already occurred, so we shouldn't change the shipping address.
$transaction_id = $order->get_transaction_id();
if ( ! empty( $transaction_id ) ) {
WC_Gateway_Paypal::log(
'Order already has a transaction ID, cannot update shipping. Order ID: ' . $order->get_id() .
'. Transaction ID: ' . $transaction_id
);
$response = $this->get_update_shipping_error_response();
return new WP_REST_Response( $response, 422 );
}
if ( ! WC()->session ) {
WC()->session = new WC_Session_Handler();
}
WC()->session->init();
// Update the shipping address before we do anything else.
$this->update_order_shipping_address( $order, $shipping_address );
// We need to rebuild the cart from the order, as we do not have session cart data
// for REST API requests.
$this->rebuild_cart_from_order( $order );
// Get the new shipping options, which depend on the new shipping address.
$updated_shipping_options = $this->get_updated_shipping_options( $order, $shipping_option );
if ( empty( $updated_shipping_options ) ) {
WC_Gateway_Paypal::log(
'No shipping options found for address. Order ID: ' . $order->get_id() .
'. Address: ' . wp_json_encode( $shipping_address )
);
$response = $this->get_update_shipping_error_response();
return new WP_REST_Response( $response, 422 );
}
// Set the chosen shipping method in the session.
if ( ! empty( $shipping_option ) ) {
WC()->session->set( 'chosen_shipping_methods', array( $shipping_option['id'] ) );
}
// Recompute fees after everything has been updated.
$this->recompute_fees( $order );
$paypal_request = new PayPalRequest( WC_Gateway_Paypal::get_instance() );
$updated_amount = $paypal_request->get_paypal_order_purchase_unit_amount( $order );
$response = array(
'id' => $paypal_order_id,
'purchase_units' => array(
array(
'reference_id' => isset( $purchase_units[0]['reference_id'] ) ? $purchase_units[0]['reference_id'] : '', // No change.
'amount' => $updated_amount,
'shipping_options' => $updated_shipping_options,
),
),
);
return new WP_REST_Response( $response, 200 );
}