|
|
@@ -0,0 +1,428 @@
|
|
|
+---
|
|
|
+title: "Checkout Flow"
|
|
|
+---
|
|
|
+
|
|
|
+import Tabs from '@theme/Tabs';
|
|
|
+import TabItem from '@theme/TabItem';
|
|
|
+
|
|
|
+Once the customer has added the desired products to the active order, it's time to check out.
|
|
|
+
|
|
|
+This guide assumes that you are using the [default OrderProcess](/guides/core-concepts/orders/#the-order-process), so
|
|
|
+if you have defined a custom process, some of these steps may be slightly different.
|
|
|
+
|
|
|
+:::note
|
|
|
+In this guide, we will assume that an `ActiveOrder` fragment has been defined, as detailed in the
|
|
|
+[Managing the Active Order guide](/guides/storefront/active-order/#define-an-order-fragment), but for the purposes of
|
|
|
+checking out the fragment should also include `customer` `shippingAddress` and `billingAddress` fields.
|
|
|
+:::
|
|
|
+
|
|
|
+## Add a customer
|
|
|
+
|
|
|
+Every order must be associated with a customer. If the customer is not logged in, then the [`setCustomerForOrder`](/reference/graphql-api/shop/mutations/#setcustomerfororder) mutation must be called. This will create a new Customer record if the provided email address does not already exist in the database.
|
|
|
+
|
|
|
+:::note
|
|
|
+If the customer is already logged in, then this step is skipped.
|
|
|
+:::
|
|
|
+
|
|
|
+<Tabs>
|
|
|
+<TabItem value="Mutation" label="Mutation" default>
|
|
|
+
|
|
|
+```graphql
|
|
|
+mutation SetCustomerForOrder($input: CreateCustomerInput!) {
|
|
|
+ setCustomerForOrder(input: $input) {
|
|
|
+ ...ActiveOrder
|
|
|
+ ...on ErrorResult {
|
|
|
+ errorCode
|
|
|
+ message
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Variables" label="Variables">
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "input": {
|
|
|
+ "title": "Mr.",
|
|
|
+ "firstName": "Bob",
|
|
|
+ "lastName": "Dobalina",
|
|
|
+ "phoneNumber": "1234556",
|
|
|
+ "emailAddress": "b.dobalina@email.com",
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+## Set the shipping address
|
|
|
+
|
|
|
+The [`setOrderShippingAddress`](/reference/graphql-api/shop/mutations/#setordershippingaddress) mutation must be called to set the shipping address for the order.
|
|
|
+
|
|
|
+
|
|
|
+<Tabs>
|
|
|
+<TabItem value="Mutation" label="Mutation" default>
|
|
|
+
|
|
|
+```graphql
|
|
|
+mutation SetOrderShippingAddress($input: CreateAddressInput!) {
|
|
|
+ setOrderShippingAddress(input: $input) {
|
|
|
+ ...ActiveOrder
|
|
|
+ ...on ErrorResult {
|
|
|
+ errorCode
|
|
|
+ message
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Variables" label="Variables">
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "input": {
|
|
|
+ "fullName": "John Doe",
|
|
|
+ "company": "ABC Inc.",
|
|
|
+ "streetLine1": "123 Main St",
|
|
|
+ "streetLine2": "Apt 4B",
|
|
|
+ "city": "New York",
|
|
|
+ "province": "NY",
|
|
|
+ "postalCode": "10001",
|
|
|
+ "countryCode": "US",
|
|
|
+ "phoneNumber": "123-456-7890",
|
|
|
+ "defaultShippingAddress": true,
|
|
|
+ "defaultBillingAddress": false
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+If the customer is logged in, you can check their existing addresses and pre-populate an address form if an existing address is found.
|
|
|
+
|
|
|
+
|
|
|
+<Tabs>
|
|
|
+<TabItem value="Query" label="Query" default>
|
|
|
+
|
|
|
+```graphql
|
|
|
+query GetCustomerAddresses {
|
|
|
+ activeCustomer {
|
|
|
+ id
|
|
|
+ addresses {
|
|
|
+ id
|
|
|
+ fullName
|
|
|
+ company
|
|
|
+ streetLine1
|
|
|
+ streetLine2
|
|
|
+ city
|
|
|
+ province
|
|
|
+ postalCode
|
|
|
+ country {
|
|
|
+ code
|
|
|
+ name
|
|
|
+ }
|
|
|
+ phoneNumber
|
|
|
+ defaultShippingAddress
|
|
|
+ defaultBillingAddress
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Result" label="Result">
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "data": {
|
|
|
+ "activeCustomer": {
|
|
|
+ "id": "123456",
|
|
|
+ "addresses": [
|
|
|
+ {
|
|
|
+ "id": "123",
|
|
|
+ "fullName": "John Doe",
|
|
|
+ "company": "",
|
|
|
+ "streetLine1": "123 Main St",
|
|
|
+ "streetLine2": "Apt 4B",
|
|
|
+ "city": "New York",
|
|
|
+ "province": "NY",
|
|
|
+ "postalCode": "10001",
|
|
|
+ "country": {
|
|
|
+ "code": "US",
|
|
|
+ "name": "United States"
|
|
|
+ },
|
|
|
+ "phoneNumber": "123-456-7890",
|
|
|
+ "defaultShippingAddress": true,
|
|
|
+ "defaultBillingAddress": false
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "id": "124",
|
|
|
+ "fullName": "John Doe",
|
|
|
+ "company": "",
|
|
|
+ "streetLine1": "456 Elm St",
|
|
|
+ "streetLine2": "",
|
|
|
+ "city": "Los Angeles",
|
|
|
+ "province": "CA",
|
|
|
+ "postalCode": "90001",
|
|
|
+ "country": {
|
|
|
+ "code": "US",
|
|
|
+ "name": "United States"
|
|
|
+ },
|
|
|
+ "phoneNumber": "987-654-3210",
|
|
|
+ "defaultShippingAddress": false,
|
|
|
+ "defaultBillingAddress": true
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+## Set the shipping method
|
|
|
+
|
|
|
+Now that we know the shipping address, we can check which shipping methods are available with the [`eligibleShippingMethods`](/reference/graphql-api/shop/queries/#eligibleshippingmethods) query.
|
|
|
+
|
|
|
+<Tabs>
|
|
|
+<TabItem value="Query" label="Query" default>
|
|
|
+
|
|
|
+```graphql
|
|
|
+query GetShippingMethods {
|
|
|
+ eligibleShippingMethods {
|
|
|
+ id
|
|
|
+ price
|
|
|
+ description
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Result" label="Result">
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "data": {
|
|
|
+ "eligibleShippingMethods": [
|
|
|
+ {
|
|
|
+ "id": "1",
|
|
|
+ "price": 545,
|
|
|
+ "description": "Standard Shipping"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "id": "2",
|
|
|
+ "price": 1250,
|
|
|
+ "description": "Expedited Shipping"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "id": "3",
|
|
|
+ "price": 1695,
|
|
|
+ "description": "Overnight Shipping"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+The results can then be displayed to the customer so they can choose the desired shipping method. If there is only a single
|
|
|
+result, then it can be automatically selected.
|
|
|
+
|
|
|
+The desired shipping method's id is the passed to the [`setOrderShippingMethod`](/reference/graphql-api/shop/mutations/#setordershippingmethod) mutation.
|
|
|
+
|
|
|
+```graphql
|
|
|
+mutation SetShippingMethod($id: [ID!]!) {
|
|
|
+ setOrderShippingMethod(shippingMethodId: $id) {
|
|
|
+ ...ActiveOrder
|
|
|
+ ...on ErrorResult {
|
|
|
+ errorCode
|
|
|
+ message
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## Add payment
|
|
|
+
|
|
|
+The [`eligiblePaymentMethods`](/reference/graphql-api/shop/queries/#eligiblepaymentmethods) query can be used to get a list of available payment methods.
|
|
|
+This list can then be displayed to the customer, so they can choose the desired payment method.
|
|
|
+
|
|
|
+
|
|
|
+<Tabs>
|
|
|
+<TabItem value="Query" label="Query" default>
|
|
|
+
|
|
|
+```graphql
|
|
|
+query GetPaymentMethods {
|
|
|
+ eligiblePaymentMethods {
|
|
|
+ id
|
|
|
+ name
|
|
|
+ code
|
|
|
+ isEligible
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Result" label="Result">
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "data": {
|
|
|
+ "eligiblePaymentMethods": [
|
|
|
+ {
|
|
|
+ "id": "1",
|
|
|
+ "name": "Stripe",
|
|
|
+ "code": "stripe",
|
|
|
+ "isEligible": true
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "id": "2",
|
|
|
+ "name": "Apple Pay",
|
|
|
+ "code": "apple-pay",
|
|
|
+ "isEligible": true
|
|
|
+ }
|
|
|
+ {
|
|
|
+ "id": "3",
|
|
|
+ "name": "Pay on delivery",
|
|
|
+ "code": "pay-on-delivery",
|
|
|
+ "isEligible": false
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+Next, we need to transition the order to the `ArrangingPayment` state. This state ensures that no other changes can be made to the order
|
|
|
+while the payment is being arranged. The [`transitionOrderToState`](/reference/graphql-api/shop/mutations/#transitionordertostate) mutation is used to transition the order to the `ArrangingPayment` state.
|
|
|
+
|
|
|
+<Tabs>
|
|
|
+<TabItem value="Query" label="Query" default>
|
|
|
+
|
|
|
+```graphql
|
|
|
+mutation TransitionToState($state: String!)
|
|
|
+ transitionOrderToState(state: $state) {
|
|
|
+ ...ActiveOrder
|
|
|
+ ...on OrderStateTransitionError {
|
|
|
+ errorCode
|
|
|
+ message
|
|
|
+ transitionError
|
|
|
+ fromState
|
|
|
+ toState
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Variables" label="Variables">
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "state": "ArrangingPayment"
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+At this point, your storefront will use an integration with the payment provider to collect the customer's payment details, and then
|
|
|
+the exact sequence of API calls will depend on the payment integration.
|
|
|
+
|
|
|
+The [`addPaymentToOrder`](/reference/graphql-api/shop/mutations/#addpaymenttoorder) mutation is the general-purpose mutation for adding a payment to an order.
|
|
|
+It accepts a `method` argument which must corresponde to the `code` of the selected payment method, and a `metadata` argument which is a JSON object containing any additional information required by that particular integration.
|
|
|
+
|
|
|
+For example, the demo data populated in a new Vendure installation includes a "Standard Payment" method, which uses the [`dummyPaymentHandler`](/reference/typescript-api/payment/dummy-payment-handler) to simulate a payment provider. Here's how you would add a payment using this method:
|
|
|
+
|
|
|
+
|
|
|
+<Tabs>
|
|
|
+<TabItem value="Mutation" label="Mutation" default>
|
|
|
+
|
|
|
+```graphql
|
|
|
+mutation AddPaymentToOrder($input: PaymentInput!) {
|
|
|
+ addPaymentToOrder(input: $input) {
|
|
|
+ ...ActiveOrder
|
|
|
+ ...on ErrorResult {
|
|
|
+ errorCode
|
|
|
+ message
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Variables" label="Variables">
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "method": "standard-payment",
|
|
|
+ "metadata": {
|
|
|
+ "shouldDecline": false,
|
|
|
+ "shouldError": false,
|
|
|
+ "shouldErrorOnSettle": false,
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+Other payment integrations have specific setup instructions you must follow:
|
|
|
+
|
|
|
+### Stripe
|
|
|
+
|
|
|
+Our [`StripePlugin docs`](/reference/typescript-api/core-plugins/payments-plugin/stripe-plugin/) describe how to set up your checkout to use Stripe.
|
|
|
+
|
|
|
+### Braintree
|
|
|
+
|
|
|
+Our [`BraintreePlugin` docs](/reference/typescript-api/core-plugins/payments-plugin/braintree-plugin/) describe how to set up your checkout to use Braintree.
|
|
|
+
|
|
|
+### Mollie
|
|
|
+
|
|
|
+Our [`MolliePlugin` docs](/reference/typescript-api/core-plugins/payments-plugin/mollie-plugin/) describe how to set up your checkout to use Mollie.
|
|
|
+
|
|
|
+### Other payment providers
|
|
|
+
|
|
|
+For more information on how to integrate with a payment provider, see the [Payment](/guides/core-concepts/payment/) guide.
|
|
|
+
|
|
|
+## Display confirmation
|
|
|
+
|
|
|
+Once the checkout has completed, the order will no longer be considered "active" (see the [`OrderPlacedStrategy`](/reference/typescript-api/orders/order-placed-strategy/)) and so the [`activeOrder`](/reference/graphql-api/shop/queries/#activeorder) query will return `null`. Instead, the [`orderByCode`](/reference/graphql-api/shop/queries/#orderbycode) query can be used to retrieve the order by its code to display a confirmation page.
|
|
|
+
|
|
|
+<Tabs>
|
|
|
+<TabItem value="Query" label="Query" default>
|
|
|
+
|
|
|
+```graphql
|
|
|
+query GetOrderByCode($code: String!) {
|
|
|
+ orderByCode(code: $code) {
|
|
|
+ ...Order
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Variables" label="Variables">
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": "PJGY46GCB1EDU9YH"
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+:::info
|
|
|
+By default Vendure will only allow access to the order by code for the first 2 hours after the order is placed if the customer is not logged in.
|
|
|
+This is to prevent a malicious user from guessing order codes and viewing other customers' orders. This can be configured via the [`OrderByCodeAccessStrategy`](/reference/typescript-api/orders/order-by-code-access-strategy/).
|
|
|
+:::
|
|
|
+
|
|
|
+
|