Use individual meta keys for Additional fields. (#46091)

* add regression tests

* Refactor to using single meta key per field

* Add changefile(s) from automation for the following project(s): woocommerce

* refactor rest of code

* keep set_array_meta same

* use correct function in condition

* fix spacing

* add docs

* fix markdown issues

* handle false bool meta

* adjust admin keys

* fix merchant tests

* handle checkboxes in checkout response

* address feedback

* Update plugins/woocommerce-blocks/docs/third-party-developers/extensibility/checkout-block/additional-checkout-fields.md

Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>

* Update plugins/woocommerce-blocks/docs/third-party-developers/extensibility/checkout-block/additional-checkout-fields.md

Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>

* Update plugins/woocommerce-blocks/docs/third-party-developers/extensibility/checkout-block/additional-checkout-fields.md

Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>

* Update plugins/woocommerce-blocks/docs/third-party-developers/extensibility/checkout-block/additional-checkout-fields.md

Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>

* Update plugins/woocommerce-blocks/docs/third-party-developers/extensibility/checkout-block/additional-checkout-fields.md

Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>

* update ledt places and reduce abstractions

---------

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>
This commit is contained in:
Seghir Nadir 2024-04-09 12:48:27 +01:00 committed by GitHub
parent fe5efc2c45
commit afee75f871
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 513 additions and 337 deletions

View File

@ -38,7 +38,7 @@ A common use-case for developers and merchants is to add a new field to the Chec
This document will outline the steps an extension should take to register some additional checkout fields.
> [!NOTE]
> [!NOTE]
> Additional Checkout fields is still in the testing phases, use it to test the API and leave feedback in this [public discussion.](https://github.com/woocommerce/woocommerce/discussions/42995)
## Available field locations
@ -85,6 +85,135 @@ By default, this block will render as the last step in the Checkout form, howeve
![The additional order information block in the post editor"](https://github.com/woocommerce/woocommerce/assets/5656702/05a3d7d9-b3af-4445-9318-443ae2c4d7d8)
## Accessing values
Additional fields are saved to individual meta keys in both the customer meta and order meta, you can access them using helper methods, or using the meta keys directly, we recommend using the helper methods, as they're less likely to change, can handle future migrations, and will support future enhancements (e.g. reading from different locations).
For address fields, two values are saved: one for shipping, and one for billing. If the customer has selected 'Use same address for billing` then the values will be the same, but still saved independently of each other.
For contact and additional fields, only one value is saved per field.
### Helper methods
`CheckoutFields` provides a function to access values from both customers and orders, it's are `get_field_from_object`.
To access a customer billing and/or shipping value:
```php
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
$field_id = 'my-plugin-namespace/my-field';
$customer = wc()->customer; // Or new WC_Customer( $id )
$checkout_fields = Package::container()->get( CheckoutFields::class );
$my_customer_billing_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'billing' );
$my_customer_shipping_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'shipping' );
```
To access an order field:
```php
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
$field_id = 'my-plugin-namespace/my-field';
$order = wc_get_order( 1234 );
$checkout_fields = Package::container()->get( CheckoutFields::class );
$my_order_billing_field = $checkout_fields->get_field_from_object( $field_id, $order, 'billing' );
$my_order_shipping_field = $checkout_fields->get_field_from_object( $field_id, $order, 'shipping' );
```
After an order is placed, the data saved to the customer and the data saved to the order will be the same. Customers can change the values for _future_ orders, or from within their My Account page. If you're looking at a customer value at a specific point in time (i.e. when the order was placed), access it from the order object, if you're looking for the most up to date value regardless, access it from the customer object.
#### Guest customers
When a guest customer places an order with additional fields, those fields will be saved to its session, so as long as the customer still has a valid session going, the values will always be there.
#### Logged-in customers
For logged-in customers, the value is only persisted once they place an order. Accessing a logged-in customer object during the place order lifecycle will return null or stale data.
If you're at a place order hook, doing this will return previous data (not the currently inserted one):
```php
$customer = new WC_Customer( $order->customer_id ); // Or new WC_Customer( 1234 )
$my_customer_billing_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'billing' );
```
Instead, always access the latest data if you want to run some extra validation/data-moving:
```php
$customer = wc()->customer // This will return the current customer with its session.
$my_customer_billing_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'billing' );
```
#### Accessing all fields
You can use `get_all_fields_from_object` to access all additional fields saved to an order or a customer.
```php
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
$order = wc_get_order( 1234 );
$checkout_fields = Package::container()->get( CheckoutFields::class );
$order_additional_billing_fields = $checkout_fields->get_all_fields_from_object( $order, 'billing' );
$order_additional_shipping_fields = $checkout_fields->get_all_fields_from_object( $order, 'shipping' );
$order_additional_fields = $checkout_fields->get_all_fields_from_object( $order, 'additional' ); // Contact and Additional are saved in the same place under the additional group.
```
This will return an array of all values, it will only include fields currently registered, if you want to include fields no longer registered, you can pass a third `true` parameter.
```php
$order = wc_get_order( 1234 );
$checkout_fields = Package::container()->get( CheckoutFields::class );
$order_additional_billing_fields = $checkout_fields->get_all_fields_from_object( $order, 'billing' ); // array( 'my-plugin-namespace/my-field' => 'my-value' );
$order_additional_billing_fields = $checkout_fields->get_all_fields_from_object( $order, 'billing', true ); // array( 'my-plugin-namespace/my-field' => 'my-value', 'old-namespace/old-key' => 'old-value' );
```
### Accessing values directly
While not recommended, you can use the direct meta key to access certain values, this is useful for external engines or page/email builders who only provide access to meta values.
Values are saved under a predefined prefix, this is needed to able to query fields without knowing which ID the field was registered under, for a field with key `'my-plugin-namespace/my-field'`, it's meta key will be the following if it's an address field:
- `_wc_billing/my-plugin-namespace/my-field`
- `_wc_shipping/my-plugin-namespace/my-field`
Or the following if it's a contact/additional field:
- `_wc_additional/my-plugin-namespace/my-field`.
Those prefixes are part of `CheckoutFields` class, and can be accessed using the following constants:
```php
echo ( CheckoutFields::BILLING_FIELDS_PREFIX ); // _wc_billing/
echo ( CheckoutFields::SHIPPING_FIELDS_PREFIX ); // _wc_shipping/
echo ( CheckoutFields::ADDITIONAL_FIELDS_PREFIX ); // _wc_additional/
```
`CheckoutFields` provides a couple of helpers to get the group name or key based on one or the other:
```php
CheckoutFields::get_group_name( "_wc_billing" ); // "billing"
CheckoutFields::get_group_name( "_wc_billing/" ); // "billing"
CheckoutFields::get_group_key( "shipping" ); // "_wc_shipping/"
```
Use cases here would be to build the key name to access the meta directly:
```php
$key = CheckoutFields::get_group_key( "additional" ) . 'my-plugin/is-opt-in';
$opted_in = get_user_meta( 123, $key, true ) === "1" ? true : false;
```
#### Checkboxes values
When accessing a checkbox values directly, it will either return `"1"` for true, `"0"` for false, or `""` if the value doesn't exist, only the provided functions will sanitize that to a boolean.
## Supported field types
The following field types are supported:
@ -428,7 +557,7 @@ It is important to note that any fields rendered in other locations will not be
|----------|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `$errors` | `WP_Error` | An error object containing errors that were already encountered while processing the request. If no errors were added yet, it will still be a `WP_Error` object but it will be empty. |
| `$fields` | `array` | The fields rendered in this locations. |
| `$group` | `'billing'\|'shipping'\|''` | If the action is for the address location, the type of address will be set here. If it is for contact or addiotional, this will be an empty string. |
| `$group` | `'billing'\|'shipping'\|'additional'` | If the action is for the address location, the type of address will be set here. If it is for contact or additional, this will be 'additional'. |
There are several places where these hooks are fired.

View File

@ -283,7 +283,9 @@ test.describe( 'Merchant → Additional Checkout Fields', () => {
// Use Locator here because the select2 box is duplicated in shipping.
await admin.page
.locator( '[id="\\/billing\\/first-plugin-namespace\\/road-size"]' )
.locator(
'[id="\\_wc_billing\\/first-plugin-namespace\\/road-size"]'
)
.selectOption( 'wide' );
// Handle changing the contact fields.
@ -335,7 +337,7 @@ test.describe( 'Merchant → Additional Checkout Fields', () => {
// Use Locator here because the select2 box is duplicated in billing.
await admin.page
.locator(
'[id="\\/shipping\\/first-plugin-namespace\\/road-size"]'
'[id="\\_wc_shipping\\/first-plugin-namespace\\/road-size"]'
)
.selectOption( 'super-wide' );

View File

@ -0,0 +1,4 @@
Significance: patch
Type: update
Use individual meta keys for Additional checkout fields.

View File

@ -220,7 +220,7 @@ class WC_Emails {
*/
public function init() {
// Include email classes.
include_once dirname( __FILE__ ) . '/emails/class-wc-email.php';
include_once __DIR__ . '/emails/class-wc-email.php';
$this->emails['WC_Email_New_Order'] = include __DIR__ . '/emails/class-wc-email-new-order.php';
$this->emails['WC_Email_Cancelled_Order'] = include __DIR__ . '/emails/class-wc-email-cancelled-order.php';
@ -610,8 +610,8 @@ class WC_Emails {
$checkout_fields = Package::container()->get( CheckoutFields::class );
$fields = array_merge(
$checkout_fields->get_order_additional_fields_with_values( $order, 'contact', '', 'view' ),
$checkout_fields->get_order_additional_fields_with_values( $order, 'additional', '', 'view' ),
$checkout_fields->get_order_additional_fields_with_values( $order, 'contact', 'additional', 'view' ),
$checkout_fields->get_order_additional_fields_with_values( $order, 'additional', 'additional', 'view' ),
);
if ( ! $fields ) {

View File

@ -35,8 +35,8 @@ class AdditionalFields extends AbstractOrderConfirmationBlock {
$content .= $this->render_additional_fields(
$controller->filter_fields_for_order_confirmation(
array_merge(
$controller->get_order_additional_fields_with_values( $order, 'contact', '', 'view' ),
$controller->get_order_additional_fields_with_values( $order, 'additional', '', 'view' ),
$controller->get_order_additional_fields_with_values( $order, 'contact', 'additional', 'view' ),
$controller->get_order_additional_fields_with_values( $order, 'additional', 'additional', 'view' ),
)
)
);

View File

@ -4,6 +4,7 @@ namespace Automattic\WooCommerce\Blocks\Domain\Services;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use WC_Customer;
use WC_Data;
use WC_Order;
use WP_Error;
@ -40,6 +41,13 @@ class CheckoutFields {
*/
private $supported_field_types = [ 'text', 'select', 'checkbox' ];
/**
* Groups of fields to be saved.
*
* @var array
*/
protected $groups = [ 'billing', 'shipping', 'additional' ];
/**
* Instance of the asset data registry.
*
@ -52,21 +60,21 @@ class CheckoutFields {
*
* @var string
*/
const BILLING_FIELDS_KEY = '_additional_billing_fields';
const BILLING_FIELDS_PREFIX = '_wc_billing/';
/**
* Shipping fields meta key.
*
* @var string
*/
const SHIPPING_FIELDS_KEY = '_additional_shipping_fields';
const SHIPPING_FIELDS_PREFIX = '_wc_shipping/';
/**
* Additional fields meta key.
*
* @var string
*/
const ADDITIONAL_FIELDS_KEY = '_additional_fields';
const ADDITIONAL_FIELDS_PREFIX = '_wc_additional/';
/**
* Sets up core fields.
@ -246,7 +254,30 @@ class CheckoutFields {
* @return array
*/
public function add_session_meta_keys( $keys ) {
return array_merge( $keys, array( self::BILLING_FIELDS_KEY, self::SHIPPING_FIELDS_KEY, self::ADDITIONAL_FIELDS_KEY ) );
$meta_keys = array();
try {
foreach ( $this->get_additional_fields() as $field_key => $field ) {
if ( 'address' === $field['location'] ) {
$meta_keys[] = self::BILLING_FIELDS_PREFIX . $field_key;
$meta_keys[] = self::SHIPPING_FIELDS_PREFIX . $field_key;
} else {
$meta_keys[] = self::ADDITIONAL_FIELDS_PREFIX . $field_key;
}
}
} catch ( \Throwable $e ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
trigger_error(
sprintf(
'Error adding session meta keys for checkout fields. %s',
esc_attr( $e->getMessage() )
),
E_USER_WARNING
);
return $keys;
}
return array_merge( $keys, $meta_keys );
}
/**
@ -272,7 +303,7 @@ class CheckoutFields {
return new WP_Error(
'woocommerce_blocks_checkout_field_required',
sprintf(
// translators: %s is field key.
// translators: %s is field key.
__( 'The field %s is required.', 'woocommerce' ),
$field['id']
)
@ -353,6 +384,7 @@ class CheckoutFields {
// Remove the field from the additional_fields array.
unset( $this->additional_fields[ $field_id ] );
}
/**
* Validates the "base" options (id, label, location) and shows warnings if they're not supplied.
*
@ -770,10 +802,10 @@ class CheckoutFields {
*
* @param array $fields Array of key value pairs of field values to validate.
* @param string $location The location being validated (address|contact|additional).
* @param string $group The group to get the field value for (shipping|billing|'') in which '' refers to the additional group.
* @param string $group The group to get the field value for (shipping|billing|additional).
* @return WP_Error
*/
public function validate_fields_for_location( $fields, $location, $group = '' ) {
public function validate_fields_for_location( $fields, $location, $group = 'additional' ) {
$errors = new WP_Error();
try {
@ -822,7 +854,7 @@ class CheckoutFields {
return new WP_Error(
'woocommerce_blocks_checkout_field_invalid',
\sprintf(
// translators: % is field key.
// translators: % is field key.
__( 'The field %s is invalid.', 'woocommerce' ),
$key
)
@ -833,7 +865,7 @@ class CheckoutFields {
return new WP_Error(
'woocommerce_blocks_checkout_field_invalid_location',
\sprintf(
// translators: %1$s is field key, %2$s location.
// translators: %1$s is field key, %2$s location.
__( 'The field %1$s is invalid for the location %2$s.', 'woocommerce' ),
$key,
$location
@ -846,7 +878,7 @@ class CheckoutFields {
return new WP_Error(
'woocommerce_blocks_checkout_field_required',
\sprintf(
// translators: %s is field key.
// translators: %s is field key.
__( 'The field %s is required.', 'woocommerce' ),
$key
)
@ -867,21 +899,35 @@ class CheckoutFields {
return array_key_exists( $key, $this->additional_fields );
}
/**
* Returns true if the given key is a valid customer field.
*
* Customer fields are fields saved to the customer data, like address and contact fields.
*
* @param string $key The field key.
*
* @return bool True if the field is valid, false otherwise.
*/
public function is_customer_field( $key ) {
return in_array( $key, array_intersect( array_merge( $this->get_address_fields_keys(), $this->get_contact_fields_keys() ), array_keys( $this->additional_fields ) ), true );
}
/**
* Persists a field value for a given order. This would also optionally set the field value on the customer object if the order is linked to a registered customer.
*
* @param string $key The field key.
* @param mixed $value The field value.
* @param WC_Order $order The order to persist the field for.
* @param string $group The group to persist the field for (shipping|billing|additional).
* @param bool $set_customer Whether to set the field value on the customer or not.
*
* @return void
*/
public function persist_field_for_order( $key, $value, $order, $set_customer = true ) {
$this->set_array_meta( $key, $value, $order );
public function persist_field_for_order( string $key, $value, WC_Order $order, string $group = 'additional', bool $set_customer = true ) {
$this->set_array_meta( $key, $value, $order, $group );
if ( $set_customer && $order->get_customer_id() ) {
$customer = new WC_Customer( $order->get_customer_id() );
$this->persist_field_for_customer( $key, $value, $customer );
$this->persist_field_for_customer( $key, $value, $customer, $group );
}
}
@ -891,11 +937,12 @@ class CheckoutFields {
* @param string $key The field key.
* @param mixed $value The field value.
* @param WC_Customer $customer The customer to persist the field for.
* @param string $group The group to persist the field for (shipping|billing|additional).
*
* @return void
*/
public function persist_field_for_customer( $key, $value, $customer ) {
$this->set_array_meta( $key, $value, $customer );
public function persist_field_for_customer( string $key, $value, WC_Customer $customer, string $group = 'additional' ) {
$this->set_array_meta( $key, $value, $customer, $group );
}
/**
@ -903,212 +950,114 @@ class CheckoutFields {
*
* @param string $key The field key.
* @param mixed $value The field value.
* @param WC_Customer|WC_Order $object The object to set the field value for.
* @param WC_Customer|WC_Order $wc_object The object to set the field value for.
* @param string $group The group to set the field value for (shipping|billing|additional).
*
* @return void
*/
private function set_array_meta( $key, $value, $object ) {
$meta_key = '';
private function set_array_meta( string $key, $value, WC_Data $wc_object, string $group ) {
$meta_key = self::get_group_key( $group ) . $key;
if ( 0 === strpos( $key, '/billing/' ) ) {
$meta_key = self::BILLING_FIELDS_KEY;
$key = str_replace( '/billing/', '', $key );
} elseif ( 0 === strpos( $key, '/shipping/' ) ) {
$meta_key = self::SHIPPING_FIELDS_KEY;
$key = str_replace( '/shipping/', '', $key );
} else {
$meta_key = self::ADDITIONAL_FIELDS_KEY;
// Convert boolean values to strings because Data Stores will skip false values.
if ( is_bool( $value ) ) {
$value = $value ? '1' : '0';
}
$meta_data = $object->get_meta( $meta_key, true );
if ( ! is_array( $meta_data ) ) {
$meta_data = [];
}
$meta_data[ $key ] = $value;
// Replacing all meta using `add_meta_data`. For some reason `update_meta_data` causes duplicate keys.
$object->add_meta_data( $meta_key, $meta_data, true );
}
/**
* Returns a field value for a given object.
*
* @param string $key The field key.
* @param WC_Customer $customer The customer to get the field value for.
* @param string $group The group to get the field value for (shipping|billing|'') in which '' refers to the additional group.
*
* @return mixed The field value.
*/
public function get_field_from_customer( $key, $customer, $group = '' ) {
return $this->get_field_from_object( $key, $customer, $group );
}
/**
* Returns a field value for a given order.
*
* @param string $field The field key.
* @param WC_Order $order The order to get the field value for.
* @param string $group The group to get the field value for (shipping|billing|'') in which '' refers to the additional group.
*
* @return mixed The field value.
*/
public function get_field_from_order( $field, $order, $group = '' ) {
return $this->get_field_from_object( $field, $order, $group );
$wc_object->add_meta_data( $meta_key, $value, true );
}
/**
* Returns a field value for a given object.
*
* @param string $key The field key.
* @param WC_Customer|WC_Order $object The customer to get the field value for.
* @param string $group The group to get the field value for (shipping|billing|'') in which '' refers to the additional group.
* @param WC_Customer|WC_Order $wc_object The customer or order to get the field value for.
* @param string $group The group to get the field value for (shipping|billing|additional).
*
* @return mixed The field value.
*/
private function get_field_from_object( $key, $object, $group = '' ) {
$meta_key = '';
if ( 0 === strpos( $key, '/billing/' ) || 'billing' === $group ) {
$meta_key = self::BILLING_FIELDS_KEY;
$key = str_replace( '/billing/', '', $key );
} elseif ( 0 === strpos( $key, '/shipping/' ) || 'shipping' === $group ) {
$meta_key = self::SHIPPING_FIELDS_KEY;
$key = str_replace( '/shipping/', '', $key );
} else {
$meta_key = self::ADDITIONAL_FIELDS_KEY;
public function get_field_from_object( string $key, WC_Data $wc_object, string $group = 'additional' ) {
$meta_key = self::get_group_key( $group ) . $key;
$meta_data = $wc_object->get_meta( $meta_key, true );
// We cast the value to a boolean if the field is a checkbox.
if ( $this->is_field( $key ) && 'checkbox' === $this->additional_fields[ $key ]['type'] ) {
return '1' === $meta_data;
}
$meta_data = $object->get_meta( $meta_key, true );
if ( ! is_array( $meta_data ) ) {
if ( null === $meta_data ) {
return '';
}
if ( ! isset( $meta_data[ $key ] ) ) {
return '';
}
return $meta_data[ $key ];
return $meta_data;
}
/**
* Returns an array of all fields values for a given customer.
* Returns an array of all fields values for a given object in a group.
*
* @param WC_Customer $customer The customer to get the fields for.
* @param bool $all Whether to return all fields or only the ones that are still registered. Default false.
* @param WC_Data $wc_object The object or order to get the fields for.
* @param string $group The group to get the fields for (shipping|billing|additional).
* @param bool $all Whether to return all fields or only the ones that are still registered. Default false.
*
* @return array An array of fields.
*/
public function get_all_fields_from_customer( $customer, $all = false ) {
$meta_data = [
'billing' => [],
'shipping' => [],
'additional' => [],
];
public function get_all_fields_from_object( WC_Data $wc_object, string $group = 'additional', bool $all = false ) {
$meta_data = [];
if ( $customer instanceof WC_Customer ) {
$meta_data['billing'] = $customer->get_meta( self::BILLING_FIELDS_KEY, true );
$meta_data['shipping'] = $customer->get_meta( self::SHIPPING_FIELDS_KEY, true );
$meta_data['additional'] = $customer->get_meta( self::ADDITIONAL_FIELDS_KEY, true );
}
return $this->format_meta_data( $meta_data, $all );
}
$prefix = self::get_group_key( $group );
/**
* Returns an array of all fields values for a given order.
*
* @param WC_Order $order The order to get the fields for.
* @param bool $all Whether to return all fields or only the ones that are still registered. Default false.
*
* @return array An array of fields.
*/
public function get_all_fields_from_order( $order, $all = false ) {
$meta_data = [
'billing' => [],
'shipping' => [],
'additional' => [],
];
if ( $order instanceof WC_Order ) {
$meta_data['billing'] = $order->get_meta( self::BILLING_FIELDS_KEY, true );
$meta_data['shipping'] = $order->get_meta( self::SHIPPING_FIELDS_KEY, true );
$meta_data['additional'] = $order->get_meta( self::ADDITIONAL_FIELDS_KEY, true );
}
return $this->format_meta_data( $meta_data, $all );
}
/**
* Returns an array of all fields values for a given meta object. It would add the billing or shipping prefix to the keys.
*
* @param array $meta The meta data to format.
* @param bool $all Whether to return all fields or only the ones that are still registered. Default false.
*
* @return array An array of fields.
*/
private function format_meta_data( $meta, $all = false ) {
$billing_fields = $meta['billing'] ?? [];
$shipping_fields = $meta['shipping'] ?? [];
$additional_fields = $meta['additional'] ?? [];
$fields = [];
if ( is_array( $billing_fields ) ) {
foreach ( $billing_fields as $key => $value ) {
if ( ! $all && ! $this->is_field( $key ) ) {
continue;
if ( $wc_object instanceof WC_Data ) {
$meta = $wc_object->get_meta_data();
foreach ( $meta as $meta_data_object ) {
if ( 0 === \strpos( $meta_data_object->key, $prefix ) ) {
$key = \str_replace( $prefix, '', $meta_data_object->key );
if ( $all || $this->is_field( $key ) ) {
$meta_data[ $key ] = $meta_data_object->value;
}
}
$fields[ '/billing/' . $key ] = $value;
}
}
if ( is_array( $shipping_fields ) ) {
foreach ( $shipping_fields as $key => $value ) {
if ( ! $all && ! $this->is_field( $key ) ) {
continue;
}
$fields[ '/shipping/' . $key ] = $value;
}
}
if ( is_array( $additional_fields ) ) {
foreach ( $additional_fields as $key => $value ) {
if ( ! $all && ! $this->is_field( $key ) ) {
continue;
}
$fields[ $key ] = $value;
}
}
return $fields;
return $meta_data;
}
/**
* From a set of fields, returns only the ones that should be saved to the customer.
* For now, this only supports fields in address location.
* Copies additional fields from an order to a customer.
*
* @param array $fields The fields to filter.
* @return array The filtered fields.
* @param WC_Order $order The order to sync the fields for.
* @param WC_Customer $customer The customer to sync the fields for.
*/
public function filter_fields_for_customer( $fields ) {
$customer_fields_keys = array_merge(
$this->get_address_fields_keys(),
$this->get_contact_fields_keys(),
);
return array_filter(
$fields,
function ( $key ) use ( $customer_fields_keys ) {
if ( 0 === strpos( $key, '/billing/' ) ) {
$key = str_replace( '/billing/', '', $key );
} elseif ( 0 === strpos( $key, '/shipping/' ) ) {
$key = str_replace( '/shipping/', '', $key );
public function sync_customer_additional_fields_with_order( WC_Order $order, WC_Customer $customer ) {
foreach ( $this->groups as $group ) {
$order_additional_fields = $this->get_all_fields_from_object( $order, $group, true );
// Sync customer additional fields with order additional fields.
foreach ( $order_additional_fields as $key => $value ) {
if ( $this->is_customer_field( $key ) ) {
$this->persist_field_for_customer( $key, $value, $customer, $group );
}
return in_array( $key, $customer_fields_keys, true );
},
ARRAY_FILTER_USE_KEY
);
}
}
}
/**
* Copies additional fields from a customer to an order.
*
* @param WC_Order $order The order to sync the fields for.
* @param WC_Customer $customer The customer to sync the fields for.
*/
public function sync_order_additional_fields_with_customer( WC_Order $order, WC_Customer $customer ) {
foreach ( $this->groups as $group ) {
$customer_additional_fields = $this->get_all_fields_from_object( $customer, $group, true );
// Sync order additional fields with customer additional fields.
foreach ( $customer_additional_fields as $key => $value ) {
if ( $this->is_field( $key ) ) {
$this->persist_field_for_order( $key, $value, $order, $group, false );
}
}
}
}
/**
* From a set of fields, returns only the ones for a given location.
*
@ -1116,15 +1065,10 @@ class CheckoutFields {
* @param string $location The location to validate the field for (address|contact|additional).
* @return array The filtered fields.
*/
public function filter_fields_for_location( $fields, $location ) {
public function filter_fields_for_location( array $fields, string $location ) {
return array_filter(
$fields,
function ( $key ) use ( $location ) {
if ( 0 === strpos( $key, '/billing/' ) ) {
$key = str_replace( '/billing/', '', $key );
} elseif ( 0 === strpos( $key, '/shipping/' ) ) {
$key = str_replace( '/shipping/', '', $key );
}
return $this->is_field( $key ) && $this->get_field_location( $key ) === $location;
},
ARRAY_FILTER_USE_KEY
@ -1151,16 +1095,16 @@ class CheckoutFields {
*
* @param WC_Order $order Order object.
* @param string $location The location to get fields for (address|contact|additional).
* @param string $group The group to get the field value for (shipping|billing|'') in which '' refers to the additional group.
* @param string $group The group to get the field value for (shipping|billing|additional).
* @param string $context The context to get the field value for (edit|view).
* @return array An array of fields definitions as well as their values formatted for display.
*/
public function get_order_additional_fields_with_values( $order, $location, $group = '', $context = 'edit' ) {
public function get_order_additional_fields_with_values( WC_Order $order, string $location, string $group = 'additional', string $context = 'edit' ) {
$fields = $this->get_fields_for_location( $location );
$fields_with_values = [];
foreach ( $fields as $field_key => $field ) {
$value = $this->get_field_from_order( $field_key, $order, $group );
$value = $this->get_field_from_object( $field_key, $order, $group );
if ( '' === $value || null === $value ) {
continue;
@ -1196,4 +1140,36 @@ class CheckoutFields {
return $value;
}
/**
* Returns a group meta prefix based on its name.
*
* @param string $group_name The group name (billing|shipping|additional).
* @return string The group meta prefix.
*/
public static function get_group_key( $group_name ) {
if ( 'billing' === $group_name ) {
return self::BILLING_FIELDS_PREFIX;
}
if ( 'shipping' === $group_name ) {
return self::SHIPPING_FIELDS_PREFIX;
}
return self::ADDITIONAL_FIELDS_PREFIX;
}
/**
* Returns a group name based on passed group key.
*
* @param string $group_key The group name (_wc_billing|_wc_shipping|_wc_additional).
* @return string The group meta prefix.
*/
public static function get_group_name( $group_key ) {
if ( 0 === \strpos( self::BILLING_FIELDS_PREFIX, $group_key ) ) {
return 'billing';
}
if ( 0 === \strpos( self::SHIPPING_FIELDS_PREFIX, $group_key ) ) {
return 'shipping';
}
return 'additional';
}
}

View File

@ -73,7 +73,9 @@ class CheckoutFieldsAdmin {
* @param \WC_Order $order The order to update the field for.
*/
public function update_callback( $key, $value, $order ) {
$this->checkout_fields_controller->persist_field_for_order( $key, $value, $order, false );
list( $group, $key ) = explode( '/', $key, 2 );
$group = CheckoutFields::get_group_name( $group );
$this->checkout_fields_controller->persist_field_for_order( $key, $value, $order, $group, false );
}
/**
@ -89,11 +91,11 @@ class CheckoutFieldsAdmin {
return $fields;
}
$group = doing_action( 'woocommerce_admin_billing_fields' ) ? 'billing' : 'shipping';
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'address', $group, $context );
$group_name = doing_action( 'woocommerce_admin_billing_fields' ) ? 'billing' : 'shipping';
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'address', $group_name, $context );
foreach ( $additional_fields as $key => $field ) {
$group_key = '/' . $group . '/' . $key;
$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $group_key );
$prefixed_key = CheckoutFields::get_group_key( $group_name ) . $key;
$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key );
}
array_splice(
@ -123,16 +125,14 @@ class CheckoutFieldsAdmin {
return $fields;
}
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', '', $context );
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', 'additional', $context );
return array_merge(
$fields,
array_map(
array( $this, 'format_field_for_meta_box' ),
$additional_fields,
array_keys( $additional_fields )
)
);
foreach ( $additional_fields as $key => $field ) {
$prefixed_key = CheckoutFields::get_group_key( 'additional' ) . $key;
$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key );
}
return array_merge( $fields, $additional_fields );
}
/**
@ -148,15 +148,13 @@ class CheckoutFieldsAdmin {
return $fields;
}
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'additional', '', $context );
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'additional', 'additional', $context );
return array_merge(
$fields,
array_map(
array( $this, 'format_field_for_meta_box' ),
$additional_fields,
array_keys( $additional_fields )
)
);
foreach ( $additional_fields as $key => $field ) {
$prefixed_key = CheckoutFields::get_group_key( 'additional' ) . $key;
$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key );
}
return array_merge( $fields, $additional_fields );
}
}

View File

@ -89,8 +89,8 @@ class CheckoutFieldsFrontend {
*/
public function render_order_additional_fields( $order ) {
$fields = array_merge(
$this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', '', 'view' ),
$this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'additional', '', 'view' ),
$this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', 'additional', 'view' ),
$this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'additional', 'additional', 'view' ),
);
if ( ! $fields ) {
@ -122,7 +122,7 @@ class CheckoutFieldsFrontend {
foreach ( $fields as $key => $field ) {
$value = $this->checkout_fields_controller->format_additional_field_value(
$this->checkout_fields_controller->get_field_from_customer( $key, $customer, $address_type ),
$this->checkout_fields_controller->get_field_from_object( $key, $customer, $address_type ),
$field
);
@ -160,8 +160,10 @@ class CheckoutFieldsFrontend {
$fields = $this->checkout_fields_controller->get_fields_for_location( 'contact' );
foreach ( $fields as $key => $field ) {
$field_key = CheckoutFields::get_group_key( 'additional' ) . $key;
$form_field = $field;
$form_field['value'] = $this->checkout_fields_controller->get_field_from_customer( $key, $customer, 'contact' );
$form_field['id'] = $field_key;
$form_field['value'] = $this->checkout_fields_controller->get_field_from_object( $key, $customer, 'contact' );
if ( 'select' === $field['type'] ) {
$form_field['options'] = array_column( $field['options'], 'label', 'value' );
@ -189,12 +191,13 @@ class CheckoutFieldsFrontend {
$additional_fields = $this->checkout_fields_controller->get_fields_for_location( 'contact' );
$field_values = array();
foreach ( $additional_fields as $key => $field ) {
if ( ! isset( $_POST[ $key ] ) ) {
foreach ( array_keys( $additional_fields ) as $key ) {
$post_key = CheckoutFields::get_group_key( 'additional' ) . $key;
if ( ! isset( $_POST[ $post_key ] ) ) {
continue;
}
$field_value = $this->checkout_fields_controller->sanitize_field( $key, wc_clean( wp_unslash( $_POST[ $key ] ) ) );
$field_value = $this->checkout_fields_controller->sanitize_field( $key, wc_clean( wp_unslash( $_POST[ $post_key ] ) ) );
$validation = $this->checkout_fields_controller->validate_field( $key, $field_value );
if ( is_wp_error( $validation ) && $validation->has_errors() ) {
@ -207,11 +210,11 @@ class CheckoutFieldsFrontend {
// Persist individual additional fields to customer.
foreach ( $field_values as $key => $value ) {
$this->checkout_fields_controller->persist_field_for_customer( $key, $value, $customer );
$this->checkout_fields_controller->persist_field_for_customer( $key, $value, $customer, 'additional' );
}
// Validate all fields for this location.
$location_validation = $this->checkout_fields_controller->validate_fields_for_location( $field_values, 'contact' );
$location_validation = $this->checkout_fields_controller->validate_fields_for_location( $field_values, 'contact', 'additional' );
if ( is_wp_error( $location_validation ) && $location_validation->has_errors() ) {
wc_add_notice( $location_validation->get_error_message(), 'error' );
@ -233,9 +236,9 @@ class CheckoutFieldsFrontend {
$fields = $this->checkout_fields_controller->get_fields_for_location( 'address' );
foreach ( $fields as $key => $field ) {
$field_key = "/{$address_type}/{$key}";
$field_key = CheckoutFields::get_group_key( $address_type ) . $key;
$address[ $field_key ] = $field;
$address[ $field_key ]['value'] = $this->checkout_fields_controller->get_field_from_customer( $key, $customer, $address_type );
$address[ $field_key ]['value'] = $this->checkout_fields_controller->get_field_from_object( $key, $customer, $address_type );
if ( 'select' === $field['type'] ) {
$address[ $field_key ]['options'] = array_column( $field['options'], 'label', 'value' );
@ -266,8 +269,8 @@ class CheckoutFieldsFrontend {
$additional_fields = $this->checkout_fields_controller->get_fields_for_location( 'address' );
$field_values = array();
foreach ( $additional_fields as $key => $field ) {
$post_key = "/{$address_type}/{$key}";
foreach ( array_keys( $additional_fields ) as $key ) {
$post_key = CheckoutFields::get_group_key( $address_type ) . $key;
if ( ! isset( $_POST[ $post_key ] ) ) {
continue;
@ -286,7 +289,7 @@ class CheckoutFieldsFrontend {
// Persist individual additional fields to customer.
foreach ( $field_values as $key => $value ) {
$this->checkout_fields_controller->persist_field_for_customer( "/{$address_type}/{$key}", $value, $customer );
$this->checkout_fields_controller->persist_field_for_customer( $key, $value, $customer, $address_type );
}
// Validate all fields for this location.

View File

@ -199,10 +199,10 @@ class CartUpdateCustomer extends AbstractCartRoute {
// We save them one by one, and we add the group prefix.
foreach ( $additional_shipping_values as $key => $value ) {
$this->additional_fields_controller->persist_field_for_customer( "/shipping/{$key}", $value, $customer );
$this->additional_fields_controller->persist_field_for_customer( $key, $value, $customer, 'shipping' );
}
foreach ( $additional_billing_values as $key => $value ) {
$this->additional_fields_controller->persist_field_for_customer( "/billing/{$key}", $value, $customer );
$this->additional_fields_controller->persist_field_for_customer( $key, $value, $customer, 'billing' );
}
wc_do_deprecated_action(
@ -244,20 +244,7 @@ class CartUpdateCustomer extends AbstractCartRoute {
$billing_country = $customer->get_billing_country();
$billing_state = $customer->get_billing_state();
$additional_fields = $this->additional_fields_controller->get_all_fields_from_customer( $customer );
$additional_fields = array_reduce(
array_keys( $additional_fields ),
function( $carry, $key ) use ( $additional_fields ) {
if ( 0 === strpos( $key, '/billing/' ) ) {
$value = $additional_fields[ $key ];
$key = str_replace( '/billing/', '', $key );
$carry[ $key ] = $value;
}
return $carry;
},
array()
);
$additional_fields = $this->additional_fields_controller->get_all_fields_from_object( $customer, 'billing' );
/**
* There's a bug in WooCommerce core in which not having a state ("") would result in us validating against the store's state.
@ -293,20 +280,8 @@ class CartUpdateCustomer extends AbstractCartRoute {
* @return array
*/
protected function get_customer_shipping_address( \WC_Customer $customer ) {
$additional_fields = $this->additional_fields_controller->get_all_fields_from_customer( $customer );
$additional_fields = $this->additional_fields_controller->get_all_fields_from_object( $customer, 'shipping' );
$additional_fields = array_reduce(
array_keys( $additional_fields ),
function( $carry, $key ) use ( $additional_fields ) {
if ( 0 === strpos( $key, '/shipping/' ) ) {
$value = $additional_fields[ $key ];
$key = str_replace( '/shipping/', '', $key );
$carry[ $key ] = $value;
}
return $carry;
},
array()
);
return array_merge(
[
'first_name' => $customer->get_shipping_first_name(),

View File

@ -428,7 +428,7 @@ class Checkout extends AbstractCartRoute {
if ( is_callable( [ $customer, $callback ] ) ) {
$customer->$callback( $value );
} elseif ( $this->additional_fields_controller->is_field( $key ) ) {
$this->additional_fields_controller->persist_field_for_customer( "/billing/$key", $value, $customer );
$this->additional_fields_controller->persist_field_for_customer( $key, $value, $customer, 'billing' );
}
}
@ -440,7 +440,7 @@ class Checkout extends AbstractCartRoute {
if ( is_callable( [ $customer, $callback ] ) ) {
$customer->$callback( $value );
} elseif ( $this->additional_fields_controller->is_field( $key ) ) {
$this->additional_fields_controller->persist_field_for_customer( "/shipping/$key", $value, $customer );
$this->additional_fields_controller->persist_field_for_customer( $key, $value, $customer, 'shipping' );
}
}

View File

@ -101,27 +101,9 @@ class BillingAddressSchema extends AbstractAddressSchema {
$billing_state = '';
}
if ( $address instanceof \WC_Order ) {
// get additional fields from order.
$additional_address_fields = $this->additional_fields_controller->get_all_fields_from_order( $address );
} elseif ( $address instanceof \WC_Customer ) {
// get additional fields from customer.
$additional_address_fields = $this->additional_fields_controller->get_all_fields_from_customer( $address );
}
$additional_address_fields = $this->additional_fields_controller->get_all_fields_from_object( $address, 'billing' );
$additional_address_fields = array_reduce(
array_keys( $additional_address_fields ),
function( $carry, $key ) use ( $additional_address_fields ) {
if ( 0 === strpos( $key, '/billing/' ) ) {
$value = $additional_address_fields[ $key ];
$key = str_replace( '/billing/', '', $key );
$carry[ $key ] = $value;
}
return $carry;
},
[]
);
$address_object = \array_merge(
$address_object = \array_merge(
[
'first_name' => $address->get_billing_first_name(),
'last_name' => $address->get_billing_last_name(),

View File

@ -261,20 +261,26 @@ class CheckoutSchema extends AbstractSchema {
* @return array
*/
protected function get_additional_fields_response( \WC_Order $order ) {
$fields = wp_parse_args(
$this->additional_fields_controller->get_all_fields_from_order( $order ),
$this->additional_fields_controller->get_all_fields_from_customer( wc()->customer )
$fields = wp_parse_args(
$this->additional_fields_controller->get_all_fields_from_object( $order, 'additional' ),
$this->additional_fields_controller->get_all_fields_from_object( wc()->customer, 'additional' )
);
$response = [];
$additional_field_schema = $this->get_additional_fields_schema();
foreach ( $fields as $key => $value ) {
if ( 0 === strpos( $key, '/billing/' ) || 0 === strpos( $key, '/shipping/' ) ) {
if ( ! isset( $additional_field_schema[ $key ] ) ) {
unset( $fields[ $key ] );
continue;
}
$response[ $key ] = $value;
// This makes sure we're casting checkboxes from "1" and "0" to boolean. In the frontend, "0" is treated as truthy.
if ( isset( $additional_field_schema[ $key ]['type'] ) && 'boolean' === $additional_field_schema[ $key ]['type'] ) {
$fields[ $key ] = (bool) $value;
} else {
$fields[ $key ] = $this->prepare_html_response( $value );
}
}
return $response;
return $fields;
}
/**
@ -418,7 +424,7 @@ class CheckoutSchema extends AbstractSchema {
foreach ( $locations as $location ) {
$location_fields = $this->additional_fields_controller->filter_fields_for_location( $fields, $location );
$result = $this->additional_fields_controller->validate_fields_for_location( $location_fields, $location );
$result = $this->additional_fields_controller->validate_fields_for_location( $location_fields, $location, 'additional' );
if ( is_wp_error( $result ) && $result->has_errors() ) {
$errors->merge_from( $result );

View File

@ -42,27 +42,9 @@ class ShippingAddressSchema extends AbstractAddressSchema {
$shipping_state = '';
}
if ( $address instanceof \WC_Order ) {
// get additional fields from order.
$additional_address_fields = $this->additional_fields_controller->get_all_fields_from_order( $address );
} elseif ( $address instanceof \WC_Customer ) {
// get additional fields from customer.
$additional_address_fields = $this->additional_fields_controller->get_all_fields_from_customer( $address );
}
$additional_address_fields = $this->additional_fields_controller->get_all_fields_from_object( $address, 'shipping' );
$additional_address_fields = array_reduce(
array_keys( $additional_address_fields ),
function( $carry, $key ) use ( $additional_address_fields ) {
if ( 0 === strpos( $key, '/shipping/' ) ) {
$value = $additional_address_fields[ $key ];
$key = str_replace( '/shipping/', '', $key );
$carry[ $key ] = $value;
}
return $carry;
},
[]
);
$address_object = array_merge(
$address_object = array_merge(
[
'first_name' => $address->get_shipping_first_name(),
'last_name' => $address->get_shipping_last_name(),

View File

@ -199,12 +199,11 @@ trait CheckoutTrait {
$errors[] = $e->getMessage();
continue;
}
$this->additional_fields_controller->persist_field_for_order( $key, $value, $this->order, false );
$this->additional_fields_controller->persist_field_for_order( $key, $value, $this->order, 'additional', false );
}
if ( $errors->has_errors() ) {
throw new RouteException( 'woocommerce_rest_checkout_invalid_additional_fields', $errors->get_error_messages(), 400 );
}
}
}

View File

@ -1,7 +1,7 @@
<?php
namespace Automattic\WooCommerce\StoreApi\Utilities;
use \Exception;
use Exception;
use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
use Automattic\WooCommerce\Blocks\Package;
@ -79,7 +79,7 @@ class OrderController {
*/
add_filter(
'woocommerce_order_get_tax_location',
function( $location ) {
function ( $location ) {
if ( ! is_null( wc()->customer ) ) {
@ -148,14 +148,11 @@ class OrderController {
'shipping_phone' => $order->get_shipping_phone(),
)
);
$order_fields = $this->additional_fields_controller->get_all_fields_from_order( $order );
$customer_fields = $this->additional_fields_controller->filter_fields_for_customer( $order_fields );
foreach ( $customer_fields as $key => $value ) {
$this->additional_fields_controller->persist_field_for_customer( $key, $value, $customer );
}
$this->additional_fields_controller->sync_customer_additional_fields_with_order( $order, $customer );
$customer->save();
};
}
}
/**
@ -202,7 +199,7 @@ class OrderController {
try {
array_walk(
$validators,
function( $validator, $index, $params ) {
function ( $validator, $index, $params ) {
call_user_func_array( array( $this, $validator ), $params );
},
array( $coupon, $order )
@ -384,20 +381,15 @@ class OrderController {
$address = $order->get_address( $address_type );
$current_locale = isset( $all_locales[ $address['country'] ] ) ? $all_locales[ $address['country'] ] : array();
$additional_fields = $this->additional_fields_controller->get_all_fields_from_order( $order );
$additional_fields = $this->additional_fields_controller->get_all_fields_from_object( $order, $address_type );
foreach ( $additional_fields as $field_id => $field_value ) {
$prefix = '/' . $address_type . '/';
if ( strpos( $field_id, $prefix ) === 0 ) {
$address[ str_replace( $prefix, '', $field_id ) ] = $field_value;
}
}
$address = array_merge( $address, $additional_fields );
$fields = $this->additional_fields_controller->get_additional_fields();
$address_fields_keys = $this->additional_fields_controller->get_address_fields_keys();
$address_fields = array_filter(
$fields,
function( $key ) use ( $address_fields_keys ) {
function ( $key ) use ( $address_fields_keys ) {
return in_array( $key, $address_fields_keys, true );
},
ARRAY_FILTER_USE_KEY
@ -759,9 +751,6 @@ class OrderController {
'shipping_phone' => wc()->customer->get_shipping_phone(),
)
);
$customer_fields = $this->additional_fields_controller->get_all_fields_from_customer( wc()->customer );
foreach ( $customer_fields as $key => $value ) {
$this->additional_fields_controller->persist_field_for_order( $key, $value, $order, false );
}
$this->additional_fields_controller->sync_order_additional_fields_with_customer( $order, wc()->customer );
}
}

View File

@ -31,6 +31,14 @@ class AdditionalFields extends MockeryTestCase {
* @var CheckoutFields
*/
protected $controller;
/**
* Products to use in tests.
*
* @var array
*/
protected $products;
/**
* Setup products and a cart, as well as register fields.
*/
@ -1469,6 +1477,7 @@ class AdditionalFields extends MockeryTestCase {
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status(), print_r( $data, true ) );
$this->assertEquals( 'sanitized-value', $data['additional_fields'][ $id ], print_r( $data, true ) );
@ -1732,4 +1741,126 @@ class AdditionalFields extends MockeryTestCase {
$this->assertEquals( 400, $response->get_status() );
$this->assertEquals( 'plugin-namespace/job-function is not one of director, engineering, customer-support, and other.', $data['data']['params']['additional_fields'], print_r( $data, true ) );
}
/**
* Ensures that saved values are returned in the cart response.
*/
public function test_previous_values_are_loaded_in_cart() {
$request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout' );
$request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) );
$request->set_body_params(
array(
'billing_address' => (object) array(
'first_name' => 'test',
'last_name' => 'test',
'company' => '',
'address_1' => 'test',
'address_2' => '',
'city' => 'test',
'state' => '',
'postcode' => 'cb241ab',
'country' => 'GB',
'phone' => '',
'email' => 'testaccount@test.com',
'plugin-namespace/gov-id' => 'billing-saved-gov-id',
),
'shipping_address' => (object) array(
'first_name' => 'test',
'last_name' => 'test',
'company' => '',
'address_1' => 'test',
'address_2' => '',
'city' => 'test',
'state' => '',
'postcode' => 'cb241ab',
'country' => 'GB',
'phone' => '',
'plugin-namespace/gov-id' => 'shipping-saved-gov-id',
),
'payment_method' => 'bacs',
'additional_fields' => array(
'plugin-namespace/job-function' => 'engineering',
'plugin-namespace/leave-on-porch' => true,
),
)
);
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status(), print_r( $data, true ) );
wc()->cart->add_to_cart( $this->products[0]->get_id(), 2 );
wc()->cart->add_to_cart( $this->products[1]->get_id(), 1 );
$request = new \WP_REST_Request( 'GET', '/wc/store/v1/cart' );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status(), print_r( $data, true ) );
$this->assertEquals( 'billing-saved-gov-id', ( (array) $data['billing_address'] )['plugin-namespace/gov-id'], print_r( $data, true ) );
$this->assertEquals( 'shipping-saved-gov-id', ( (array) $data['shipping_address'] )['plugin-namespace/gov-id'], print_r( $data, true ) );
}
/**
* Ensures that saved values are returned in the checkout response.
*/
public function test_previous_values_are_loaded_in_checkout() {
$request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout' );
$request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) );
$request->set_body_params(
array(
'billing_address' => (object) array(
'first_name' => 'test',
'last_name' => 'test',
'company' => '',
'address_1' => 'test',
'address_2' => '',
'city' => 'test',
'state' => '',
'postcode' => 'cb241ab',
'country' => 'GB',
'phone' => '',
'email' => 'testaccount@test.com',
'plugin-namespace/gov-id' => 'billing-saved-gov-id',
),
'shipping_address' => (object) array(
'first_name' => 'test',
'last_name' => 'test',
'company' => '',
'address_1' => 'test',
'address_2' => '',
'city' => 'test',
'state' => '',
'postcode' => 'cb241ab',
'country' => 'GB',
'phone' => '',
'plugin-namespace/gov-id' => 'shipping-saved-gov-id',
),
'payment_method' => 'bacs',
'additional_fields' => array(
'plugin-namespace/job-function' => 'engineering',
'plugin-namespace/leave-on-porch' => true,
),
)
);
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status(), print_r( $data, true ) );
wc()->cart->add_to_cart( $this->products[0]->get_id(), 2 );
wc()->cart->add_to_cart( $this->products[1]->get_id(), 1 );
$request = new \WP_REST_Request( 'GET', '/wc/store/v1/checkout' );
$request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status(), print_r( $data, true ) );
$this->assertEquals( 'billing-saved-gov-id', ( (array) $data['billing_address'] )['plugin-namespace/gov-id'], print_r( $data, true ) );
$this->assertEquals( 'shipping-saved-gov-id', ( (array) $data['shipping_address'] )['plugin-namespace/gov-id'], print_r( $data, true ) );
$this->assertEquals( 'engineering', ( (array) $data['additional_fields'] )['plugin-namespace/job-function'], print_r( $data, true ) );
$this->assertArrayNotHasKey( 'plugin-namespace/leave-on-porch', $data['additional_fields'], print_r( $data, true ) );
}
}