This repository has been archived by the owner on Feb 23, 2024. It is now read-only.
Add documentation for ExperimentalDiscountsMeta
, cart/extensions
endpoint, and extensionCartUpdate
#4377
Merged
Merged
Add documentation for ExperimentalDiscountsMeta
, cart/extensions
endpoint, and extensionCartUpdate
#4377
Changes from 8 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
0197e1e
Add documentation for `cart/extensions`
opr fc2a73e
Add documentation for ExperimentalDiscountsMeta slot
opr 074ed98
Add clearer information to `cart/extensions` documentation
opr b1e6b07
Change checkout screenshot
opr a81ddf3
Change checkout screenshot
opr 7b658e5
Update documentation into new template and amend based on suggestions
opr db0c4f5
Fix typo
opr 5907df9
Fix punctuation and remove passive-aggressive voice!
opr 4e2227b
Update docs/extensibility/extend-rest-api-update-cart.md
opr File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
# Updating the cart with the Store API | ||
|
||
## The problem | ||
You're an extension developer, and your extension does some server-side processing as a result of some client-side | ||
input, i.e. a shopper filling in an input field in the Cart sidebar, and then pressing a button. | ||
|
||
This server-side processing causes the state of the cart to change, and you want to update the data displayed | ||
in the client-side Cart or Checkout block. | ||
|
||
You can't simply update the client-side cart state yourself. This is restricted to prevent malfunctioning extensions | ||
inadvertently updating it with malformed or invalid data which will cause the whole block to break. | ||
|
||
## The solution | ||
`ExtendRestApi` offers the ability for extensions to register callback functions to be executed when | ||
signalled to do so by the client-side Cart or Checkout. | ||
|
||
WooCommerce Blocks also provides a front-end function called `extensionCartUpdate` which can be called by client-side | ||
code, this will send data (specified by you when calling `extensionCartUpdate`) to the `cart/extensions` endpoint. | ||
When this endpoint gets hit, any relevant (based on the namespace provided to `extensionCartUpdate`) callbacks get | ||
executed, and the latest server-side cart data gets returned and the block is updated with this new data. | ||
|
||
## Basic usage | ||
In your extension's server-side integration code: | ||
```PHP | ||
use Automattic\WooCommerce\Blocks\Package; | ||
use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi; | ||
|
||
add_action('woocommerce_blocks_loaded', function() { | ||
// ExtendRestApi is stored in the container as a shared instance between the API and consumers. | ||
// You shouldn't initiate your own ExtendRestApi instance using `new ExtendRestApi` but should always use the shared instance from the Package dependency injection container. | ||
$extend = Package::container()->get( ExtendRestApi::class ); | ||
$extend->register_update_callback( | ||
[ | ||
'namespace' => 'extension-unique-namespace', | ||
'callback' => /* Add your callable here */ | ||
] | ||
); | ||
} ); | ||
``` | ||
|
||
and on the client side: | ||
```typescript | ||
import { extensionCartUpdate } from '@woocommerce/blocks-checkout'; | ||
opr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
extensionCartUpdate( | ||
{ | ||
namespace: 'extension-unique-namespace', | ||
data: { | ||
key: 'value', | ||
another_key: 100, | ||
third_key: { | ||
fourth_key: true, | ||
} | ||
} | ||
} | ||
) | ||
``` | ||
|
||
## Things to consider | ||
|
||
### Extensions cannot update the client-side cart state themselves | ||
You may be wondering why it's not possible to just make a custom AJAX endpoint for your extension that will update the | ||
cart. As mentioned, extensions are not permitted to update the client-side cart's state, because doing this incorrectly | ||
would cause the entire block to break, preventing the user from continuing their checkout. Instead you _must_ do this | ||
through the `extensionCartUpdate` function. | ||
|
||
### Only one callback for a given namespace may be registered | ||
With this in mind, if your extension has several client-side interactions that result in different code paths being | ||
executed on the server-side, you may wish to pass additional data through in `extensionsCartUpdate`. For example | ||
if you have two actions the user can take, one to _add_ a discount, and the other to _remove_ it, you may wish to pass | ||
a key called `action` along with the other data to `extensionsCartUpdate`. Then in your callback, you can check this | ||
value to distinguish which code path you should execute. | ||
|
||
Example: | ||
```PHP | ||
<?php | ||
|
||
use Automattic\WooCommerce\Blocks\Package; | ||
use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi; | ||
|
||
function add_discount() { | ||
/* Do some processing here */ | ||
} | ||
function remove_discount() { | ||
/* Do some processing here */ | ||
} | ||
|
||
add_action('woocommerce_blocks_loaded', function() { | ||
// ExtendRestApi is stored in the container as a shared instance between the API and consumers. | ||
// You shouldn't initiate your own ExtendRestApi instance using `new ExtendRestApi` but should always use the shared instance from the Package dependency injection container. | ||
$extend = Package::container()->get( ExtendRestApi::class ); | ||
$extend->register_update_callback( | ||
[ | ||
'namespace' => 'extension-unique-namespace', | ||
'callback' => function( $data ) { | ||
if ( $data['action'] === 'add' ) { | ||
add_discount( ); | ||
} | ||
if ( $data['action'] === 'remove' ) { | ||
remove_discount(); | ||
} | ||
} | ||
] | ||
); | ||
} ); | ||
``` | ||
If you try to register again, under the same namespace, the previously registered callback will be overwritten. | ||
|
||
## API Definition | ||
|
||
### PHP | ||
`ExtendRestApi::register_update_callback`: Used to register a callback to be executed when the `cart/extensions` | ||
endpoint gets hit with a given namespace. It takes an array of arguments | ||
|
||
| Attribute | Type | Required | Description | | ||
|---|---|---|---| | ||
| `namespace` | `string` | Yes | The namespace of your extension. This is used to determine which extension's callbacks should be executed. | | ||
| `callback` | `Callable` | Yes | The function/method (or Callable) that will be executed when the `cart/extensions` endpoint is hit with a `namespace` that matches the one supplied. The callable should take a single argument. The data passed into the callback via this argument will be an array containing whatever data you choose to pass to it. The callable does not need to return anything, if it does, then its return value will not be used. | ||
|
||
### JavaScript | ||
|
||
`extensionCartUpdate`: Used to signal that you want your registered callback to be executed, and to pass data to the callback. It takes an object as its only argument. | ||
|
||
| Attribute | Type | Required | Description | | ||
|---|---|---|---| | ||
| `namespace` | `string` | Yes | The namespace of your extension. This is used to determine which extension's callbacks should be executed. | | ||
| `data` | `Object` | No | The data you want to pass to your callback. Anything in the `data` key will be passed as the first (and only) argument to your callback as an associative array. | ||
|
||
## Putting it all together | ||
You are the author of an extension that lets the shopper redeem points that they earn on your website for a discount on | ||
their order. There is a text field where the shopper can enter how many points they want to redeem, and a submit button | ||
that will apply the redemption. | ||
|
||
Your extension adds these UI elements to the sidebar in the Cart and Checkout blocks using the [`DiscountsMeta`](./available-slot-fills.md) Slot. | ||
|
||
More information on how to use Slots is available in our [Slots and Fills documentation](./slot-fills.md). | ||
|
||
Once implemented, the sidebar has a control added to it like this: | ||
|
||
<img src="https://user-images.githubusercontent.com/5656702/125109827-bf7c8300-e0db-11eb-9e51-59921b38a0c2.png" width=400 /> | ||
|
||
### The "Redeem" button | ||
In your UI, you are tracking the value the shopper enters into the `Enter amount` box using a React `useState` variable. | ||
The variable in this example shall be called `pointsInputValue`. | ||
|
||
When the `Redeem` button gets clicked, you want to tell the server how many points to apply to the shopper's basket, | ||
based on what they entered into the box, apply the relevant discount, update the server-side cart, and then show the | ||
updated price in the client-side sidebar. | ||
|
||
To do this, you will need to use `extensionCartUpdate` to tell the server you want to execute your callback, and have | ||
the new cart state loaded into the UI. The `onClick` handler of the button may look like this: | ||
|
||
```javascript | ||
import { extensionCartUpdate } from '@woocommerce/blocks-checkout'; | ||
|
||
const buttonClickHandler = () => { | ||
extensionCartUpdate( | ||
{ | ||
namespace: 'super-coupons', | ||
data: { | ||
pointsInputValue | ||
} | ||
} | ||
) | ||
}; | ||
``` | ||
|
||
### Registering a callback to run when the `cart/extensions` endpoint is hit | ||
So far, we haven't registered a callback with WooCommerce Blocks yet, so when `extensionCartUpdate` causes the | ||
`cart/extensions` endpoint to get hit, nothing will happen. | ||
|
||
Much like adding data to the Store API (described in more detail in | ||
[Exposing your data in the Store API](./extend-rest-api-add-data.md).) we can add the callback | ||
by invoking the `register_update_callback` method on the `ExtendRestApi` class from WooCommerce Blocks. | ||
|
||
We have written a function called `redeem_points` which applies a discount to the WooCommerce cart. This function does | ||
not return anything. Note, the actual implementation of this function is not the focus of this document, so has been | ||
omitted. All that is important to note is that it modifies the WooCommerce cart. | ||
|
||
```PHP | ||
<?php | ||
|
||
use Automattic\WooCommerce\Blocks\Package; | ||
use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi; | ||
|
||
function redeem_points( $points ) { | ||
/* Do some processing here that applies a discount to the WC cart based on the value of $points */ | ||
} | ||
|
||
add_action('woocommerce_blocks_loaded', function() { | ||
// ExtendRestApi is stored in the container as a shared instance between the API and consumers. | ||
// You shouldn't initiate your own ExtendRestApi instance using `new ExtendRestApi` but should always use the shared instance from the Package dependency injection container. | ||
$extend = Package::container()->get( ExtendRestApi::class ); | ||
$extend->register_update_callback( | ||
[ | ||
'namespace' => 'super-coupons', | ||
'callback' => function( $data ) { | ||
redeem_points( $data['points'] ); | ||
}, | ||
] | ||
); | ||
} ); | ||
``` | ||
|
||
Now that this is registered, when the button is pressed, the `cart/extensions` endpoint is hit, with a `namespace` of | ||
`super-coupons` our `redeem_points` function will be executed. After this has finished processing, the client-side cart | ||
will be updated by WooCommerce Blocks. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To keep consistency with the rest of our codebase, can we frame this doc as:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Nadir, I've updated the document now to follow this format. I feel like there's going to be way too much to cover in the
Putting it all together
section, and a lot of irrelevant information not relating to this part of the API. I did not want to go into too much detail about the things not directly related to this, so I've omitted some parts that are covered in other documents, and also not gone into detail about the implementation of my example methods.I also feel like there's quite a bit of overlap and duplication of the
Basic usage
andPutting it all together
section, but this is probably OK.