question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

RFC: Add orderToken to checkout queries and mutation to be more stateless

See original GitHub issue

Is your feature request related to a problem? Please describe. Currently all queries and mutations that are related to the checkout are only working with the active order of a customer. So the active order is basically resolved via the session token. This behaviour leads to following problems:

  • Sharing a checkout URL inside an organization is not possible (B2B eCommerce)
  • Abandoned cart/checkout emails are a hassle because there is no definite URL for every checkout

Other established eCommerce systems are using the checkout or cart token in the checkout URL for that reason. For example the Shopify Checkout is pretty much stateless because there is the checkoutToken as a parameter in the URL.

Describe the solution you’d like Basically introduce an orderToken property for every checkout-related query and mutation.

The business logic in the background should use the orderToken if given. If not it should fall back to the active order of the customer. Also there should be an service, that determines if the currently authenticated customer is eligible to access the order.

By default it should check if the authenticated customer is the same as the customer of the order. The service that is doing that eligibility check should be designed like the OrderByCodeAccessStrategy (https://www.vendure.io/docs/typescript-api/orders/order-by-code-access-strategy/) so that you can easily override it.

Describe alternatives you’ve considered There are no alternatives than rewriting every checkout-related mutation and query, which is basically nonesense because 95% of the code would stay the same.

Issue Analytics

  • State:closed
  • Created a year ago
  • Reactions:2
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
michaelbromleycommented, Nov 4, 2022

Some initial thoughts on design:

  1. What we are defining here is a way to specify the active order in the shop API operations. Currently, as you say, the active order is implicit based on the active session.
  2. The basic capability we currently lack is the ability to control how the active order is determined. This, as you say, can be delegated to a strategy to make it configurable.

Affected operations

The following operations of the Shop API are affected by this change:

  • activeOrder
  • addItemToOrder
  • adjustOrderLine
  • removeOrderLine
  • removeAllOrderLines
  • applyCouponCode
  • removeCouponCode
  • addPaymentToOrder
  • setCustomerForOrder
  • setOrderShippingAddress
  • setOrderBillingAddress
  • eligibleShippingMethods
  • eligiblePaymentMethods
  • setOrderShippingMethod
  • setOrderCustomFields
  • nextOrderStates
  • transitionOrderToState

Proposal: ActiveOrderStrategy

A strategy interface which defines how the active order is determined when executing the affected operations

export interface ActiveOrderStrategy extends InjectableStrategy {
  determineActiveOrder(ctx: RequestContext, inputs: any): Promise<Order | undefined>;
}

The default strategy, which implements the existing behaviour, would look like this:

export class DefaultActiveOrderStrategy implements ActiveOrderStrategy {
  private activeOrderService: ActiveOrderService;

  init(injector) {
    this.activeOrderService = injector.get(ActiveOrderService);
  }

  determineActiveOrder(ctx) {
    return this.activeOrderService.getOrderFromContext(ctx);
  }
}

Supplying an order token

The first question that arises is how do we supply an order token to these operations?

  1. A GraphQL input. We could define a new input type ActiveOrderInput which can then be extended by the developer to include whatever fields they need, e.g.
    extend input ActiveOrderInput {
       orderToken: String!
    }
    
    We would then need to add this as an optional argument to each of the affected operations, e.g.
    type Query {
    -  activeOrder: Order
    +  activeOrder(activeOrder: ActiveOrderInput): Order
    }
    
    type Mutation {
    -  addItemToOrder(productVariantId: ID!, quantity: Int!): UpdateOrderItemsResult!
    +  addItemToOrder(productVariantId: ID!, quantity: Int!, activeOrder: ActiveOrderInput): UpdateOrderItemsResult!
    }
    
  2. In the request url. For example, we could add the order token (or whatever other data is needed by the strategy) to the request URL. This would then not require any change at all to the Shop API schema. These url values can be accessed via the ctx object in the strategy. The downsides to this approach are that it is not type-safe, less expressive than a GraphQL input, and feels slightly hacky IMO.

Example orderToken implementation

Let’s assume we go with the GraphQL input method of supplying the token. What would a more-or-less complete implementation look like?

// define a token custom field on Order
config.customFields.Order.push({
  name: 'orderToken',
  type: 'string',
  internal: true,
});


// extend the input to accept the order token
const shopApiExtensions = gql`
  extend input ActiveOrderInput {
    orderToken: String!
  }
`;

// define the strategy itself
export class TokenActiveOrderStrategy implements ActiveOrderStrategy {
  private connection: TransactionalConnection;

  init(injector) {
    this.connection= injector.get(TransactionalConnection);
  }

  async determineActiveOrder(ctx, input: { orderToken: string }) {
    if (!ctx.session) {
      throw new InternalServerError(`error.no-active-session`);
    }
    const order = this.connection.getRepository(ctx, Order).findOne({
      relations: ['customer'],
      where: { 
         customFields: { orderToken: input.orderToken },
      },
    });
    if(!order) {
      return;
    }
    const orderUserId = order.customer && order.customer.user && order.customer.user.id;
    if (idsAreEqual(ctx.activeUserId, orderUserId)) {
      return order;
    } else {
      return;
    }
  }
}

Feedback wanted

This is quite a significant change but could unlock lots of new use-cases! I’d love to get feedback on my ideas outlined above. Please leave any comments down below 👍

1reaction
tianyingchuncommented, Nov 5, 2022

Good feature Graphql input is best

Read more comments on GitHub >

github_iconTop Results From Across the Web

[RFC] Change how queries/mutations access ctx and ... - GitHub
Currently queries/mutations access ctx as shown below. Data and functions can be added to ctx via HTTP Middleware.
Read more >
Stateful and Stateless Applications and its Best Practices
Stateful vs Stateless Session. These both store state from client requests on the server itself and use that state to process further requests....
Read more >
Stateful vs stateless - Red Hat
Whether something is stateful or stateless depends on how long the state of interaction with it is being recorded and how that information ......
Read more >
Search Results - CVE
This vulnerability allows attackers to execute arbitrary web scripts or HTML via crafted payload injected into the Name field under the Create New...
Read more >
Can't add items to cart after reaching the payment checkout step
If you go through the checkout process, up to payment screen, ... vendure RFC: Add orderToken to checkout queries and mutation to be...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found