-
Notifications
You must be signed in to change notification settings - Fork 153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ongoing discussion] Draft for cart operations and other GraphQL improvements #15
Changes from all commits
d8e9846
30ef22f
dc06523
d638fa9
902e999
43829b3
7d71efc
83aa880
c6f7d84
47b9607
bdd70b4
c5aaccb
c87159c
f2ca044
10ce77e
5cc770f
b0bc80e
fa08ab1
5a831aa
a940475
299809a
9635092
f0e011a
ca3a063
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
type Query { | ||
cart(input: CartQueryInput): CartQueryOutput | ||
} | ||
|
||
input CartQueryInput { | ||
cart_id: String! | ||
} | ||
|
||
type CartQueryOutput { | ||
cart: Cart | ||
} | ||
|
||
type Cart { | ||
id: String! | ||
|
||
line_items_count: Int! | ||
items_quantity: Float! | ||
|
||
selected_payment_method: CheckoutPaymentMethod | ||
available_payment_methods: [CheckoutPaymentMethod]! | ||
|
||
customer: CheckoutCustomer | ||
customer_notes: String | ||
|
||
gift_cards_amount_used: Money | ||
applied_gift_cards: [CartGiftCard] | ||
|
||
is_multishipping: Boolean! | ||
is_virtual: Boolean! | ||
} | ||
|
||
type CheckoutCustomer { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be great to use polymorphism here.
React components could then require different fragments depending on the type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea in general. I don't understand though, why Please clarify which exact fields should be different between guest and customer implementations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that the The idea of having such a structure, is to open rooms for extension in the shop. For instance in a B2B context, a |
||
is_guest: Boolean! | ||
email: String! | ||
prefix: String | ||
first_name: String! | ||
last_name: String! | ||
middle_name: String | ||
suffix: String | ||
gender: GenderEnum | ||
date_of_birth: String | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there any plans for a scalar There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do not have plans for that, but I know that this topic was discussed in scope of Webonyx project: webonyx/graphql-php#228 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, thank you. |
||
vat_number: String # Do we need it at all on storefront? Do we need more details | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The presence of a vat_number can influence the rendering in many ways, so I'd say yes. |
||
} | ||
|
||
enum GenderEnum { | ||
MALE | ||
FEMALE | ||
} | ||
|
||
type CheckoutPaymentMethod { | ||
code: String! | ||
label: String! | ||
balance: Money | ||
sort_order: Int | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a use case for exposing this value (vs having sort params in queries returning this type)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We thought of a use case when the client app needs to honor sort order of available payment methods during rendering. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this pattern is better to be delegated to the server logic. By default If a use case where methods should be sorted differently, it could lead to a different signature with an optional param:
|
||
} | ||
|
||
type CartGiftCard { | ||
code: String! | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
type Query { | ||
setBillingAddressOnCart(input: SetBillingAddressOnCartInput): SetBillingAddressOnCartOutput | ||
setShippingAddressesOnCart(input: SetShippingAddressesOnCartInput): SetShippingAddressesOnCartOutput | ||
setShippingMethodsOnCart(input: SetShippingMethodsOnCartInput): SetShippingMethodsOnCartOutput | ||
} | ||
|
||
input SetShippingMethodsOnCartInput { | ||
shipping_methods: [ShippingMethodForAddressInput!]! | ||
} | ||
|
||
input ShippingMethodForAddressInput { | ||
cart_address_id: String! | ||
shipping_method_code: String! | ||
} | ||
|
||
input SetBillingAddressOnCartInput { | ||
customer_address_id: Int | ||
address: CartAddressInput | ||
} | ||
|
||
input SetShippingAddressesOnCartInput { | ||
customer_address_id: Int # Can be provided in one-page checkout and is required for multi-shipping checkout | ||
address: CartAddressInput | ||
cart_items: [CartItemQuantityInput!] | ||
} | ||
|
||
input CartItemQuantityInput { | ||
cart_item_id: String! | ||
quantity: Float! | ||
} | ||
|
||
input CartAddressInput { | ||
firstname: String! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How are we going to add customer address attributes? |
||
lastname: String! | ||
company: String | ||
street: [String!]! | ||
city: String! | ||
region: String | ||
postcode: String | ||
country_code: String! | ||
telephone: String! | ||
save_in_address_book: Boolean! | ||
} | ||
|
||
type SetShippingAddressesOnCartOutput { | ||
cart: Cart! | ||
} | ||
|
||
type SetShippingMethodsOnCartOutput { | ||
cart: Cart! | ||
} | ||
|
||
type SetBillingAddressOnCartOutput { | ||
cart: Cart! | ||
} | ||
|
||
type Cart { | ||
addresses: [CartAddress]! | ||
} | ||
|
||
type CartAddress { | ||
firstname: String! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how are we going to set information about customer address attributes |
||
lastname: String! | ||
company: String | ||
street: [String!]! | ||
city: String! | ||
region: CartAddressRegion | ||
postcode: String | ||
country: CartAddressCountry! | ||
telephone: String! | ||
address_type: AdressTypeEnum! | ||
|
||
selected_shipping_method: CheckoutShippingMethod | ||
available_shipping_methods: [CheckoutShippingMethod]! | ||
|
||
items_weight: Float | ||
customer_notes: String | ||
gift_cards_amount_used: Money | ||
applied_gift_cards: [CartGiftCard] | ||
|
||
cart_items: [CartItemQuantity] | ||
} | ||
|
||
type CartItemQuantity { | ||
cart_item_id: String! | ||
quantity: Float! | ||
} | ||
|
||
type CartAddressCountry { | ||
code: String | ||
label: String | ||
} | ||
|
||
type CartAddressRegion { | ||
code: String | ||
label: String | ||
} | ||
|
||
enum AdressTypeEnum { | ||
SHIPPING | ||
BILLING | ||
} | ||
|
||
type CheckoutShippingMethod { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we also need to add information about the error message, it can be displayed if in the system configuration we choose carriers/dhl/showmethod = 1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. case code/label become optional if error message is displayed (setting "show method if not applicable") There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
code: String | ||
label: String | ||
free_shipping: Boolean! | ||
error_message: String | ||
# TODO: Add more complex structure for shipping rates | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
interface CartItemInterface { | ||
prices: CartItemPrices | ||
} | ||
|
||
type Cart { | ||
prices: CartPrices | ||
} | ||
|
||
type CartAddress { | ||
prices: CartAddressPrices | ||
# additional fields will be added later | ||
} | ||
|
||
interface CartPricesInterface { | ||
grand_total: Money | ||
|
||
# price display settings should be requested via store config query | ||
subtotal_including_tax: Money | ||
subtotal_excluding_tax: Money | ||
|
||
subtotal_with_discount_excluding_tax: Money | ||
discount_tax_compensation: Money #Should we have subtotal with discount including tax instead, is it different from grand_total? | ||
|
||
applied_taxes: [CartTaxItem]! # Should include regular taxes and WEEE taxes | ||
applied_discounts: [CartDiscountItem]! | ||
} | ||
|
||
type CartItemPrices implements CartPricesInterface { | ||
price_including_tax: Money | ||
price_excluding_tax: Money | ||
|
||
custom_price: Money | ||
} | ||
|
||
type CartAddressPrices implements CartPricesInterface { | ||
|
||
shipping_including_tax: Money | ||
shipping_excluding_tax: Money | ||
|
||
shipping_discount: Money # Do we need shipping_with_discount_including_tax? | ||
shipping_discount_tax_compensation: Money | ||
} | ||
|
||
type CartPrices implements CartPricesInterface { | ||
} | ||
|
||
type CartTaxItem { | ||
amount: Money! | ||
label: String! | ||
} | ||
|
||
type CartDiscountItem { | ||
amount: Money! | ||
label: String! | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
type Mutation { | ||
applyCouponToCart(input: ApplyCouponToCartInput): ApplyCouponToCartOutput | ||
removeCouponFromCart(input: RemoveCouponFromCartInput): RemoveCouponFromCartOutput | ||
} | ||
|
||
input ApplyCouponToCartInput { | ||
cart_id: String! | ||
coupon_code: String! | ||
} | ||
|
||
type ApplyCouponToCartOutput { | ||
cart: Cart! | ||
} | ||
|
||
type Cart { | ||
applied_coupon: AppliedCoupon | ||
} | ||
|
||
type CartAddress { | ||
applied_coupon: AppliedCoupon | ||
} | ||
|
||
type AppliedCoupon { | ||
# Wrapper allows for future extension of coupon info | ||
code: String! | ||
} | ||
|
||
input RemoveCouponFromCartInput { | ||
cart_id: String! | ||
} | ||
|
||
type RemoveCouponFromCartOutput { | ||
cart: Cart | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
**Overview** | ||
|
||
As a Magento developer, I need to manipulate the shopping cart via GraphQL so that I can programmatically create orders on behalf of a shopper. | ||
|
||
GraphQL needs to provide sufficient mutations (ways to create/update/delete data) for a developer to build out the storefront checkout experience for a shopper. | ||
|
||
**Use cases:** | ||
- Both guest and registered shoppers can add new items to cart | ||
- Both guest and registered shoppers can update item qty in cart | ||
- Both guest and registered shoppers can remove items from cart | ||
- Both guest and registered shoppers can update the configuration (for a configurable product) or quantity of a previously added configurable product in cart | ||
- Edit Item link > Product page > Update configuration or qty > Update Cart | ||
|
||
**Main decision points:** | ||
|
||
- Separate mutations for each product type while adding items to cart. Each operation will be supporting bulk use case | ||
- Uniform interface for guest vs customer | ||
- Separate mutations for each checkout step | ||
- Create empty cart | ||
- Add items to cart | ||
- Set shipment method | ||
- Set payment method | ||
- Set addresses | ||
- Same granularity for updates and removals | ||
- Possibility to combine mutations for checkout steps | ||
- Can create "order in one call" mutation in the future if needed | ||
- Hashed IDs for cart items | ||
- Single input object | ||
- Async nature of the flow must be supported on the client side (via AJAX calls) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might want to expand on this point more, since all requests from the browser are async (barring sync Maybe add some additional language around this clarifying that it creates a "job" of sorts, which helps imply that the results of the mutation will be some object representing a task that will be finished some time in the future. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Asynchronous server side implementation is not approved yet. The schema may change, if it will be approved. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's important to include why there is a desire for these mutations to be async. When we chatted briefly yesterday, I got the impression that it's because this could be a long-running operation, and we don't want to keep the request open. Is that accurate? It's just worth making the justification clear, because a concept of a "job" that needs to be polled (or a subscription) pushes some complexity to the consumer of the API. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not necessarily long-running, may be just a lot of concurrent requests during peak hours. Decision on the async server side is not made yet. |
||
- Server-side asynchronous mutations can be supported in the future on framework level in a way similar to Async REST. | ||
|
||
**Proposed schema for adding items to cart:** | ||
|
||
- [AddSimpleProductToCart](AddSimpleProductToCart.graphqls) | ||
- [AddBundleProductToCart](AddBundleProductToCart.graphqls) | ||
- [AddConfigurableProductToCart](AddConfigurableProductToCart.graphqls) | ||
- [AddDownloadableProductToCart](AddDownloadableProductToCart.graphqls) | ||
- [AddGiftCardProductToCart](AddGiftCardProductToCart.graphqls) | ||
- [AddGroupedProductToCart](AddGroupedProductToCart.graphqls) | ||
- [AddVirtualProductToCart](AddVirtualProductToCart.graphqls) | ||
|
||
|
||
**My Account area impacted:** | ||
- Cart | ||
- Minicart |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
type Mutation { | ||
addBundleProductsToCart(input: AddBundleProductsToCartInput): AddBundleProductsToCartOutput | ||
updateBundleProductsInCart(input: UpdateBundleProductsInCartInput): UpdateBundleProductsInCartOutput | ||
} | ||
|
||
input UpdateBundleProductsInCartInput { | ||
cart_id: String! | ||
cartItems: [UpdateBundleProductCartItemInput!]! | ||
} | ||
|
||
input UpdateBundleProductCartItemInput { | ||
details: UpdateCartItemDetailsInput! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it will be better to use
|
||
bundle_options:[BundleOptionInput!] | ||
customizable_options:[CustomizableOptionInput] | ||
} | ||
|
||
input AddBundleProductsToCartInput { | ||
cart_id: String! | ||
cartItems: [BundleProductCartItemInput!]! | ||
} | ||
|
||
input BundleProductCartItemInput { | ||
details: CartItemDetailsInput! | ||
bundle_options:[BundleOptionInput!]! | ||
customizable_options:[CustomizableOptionInput!] | ||
} | ||
|
||
input BundleOptionInput { | ||
id: Int! | ||
quantity: Float! | ||
value: [String!]! | ||
} | ||
|
||
type AddBundleProductsToCartOutput { | ||
cart: Cart! | ||
} | ||
|
||
type BundleCartItem implements CartItemInterface { | ||
customizable_options: [SelectedCustomizableOption]! | ||
bundle_options: [SelectedBundleOption!]! | ||
} | ||
|
||
type SelectedBundleOption { | ||
id: Int! | ||
label: String! | ||
type: String! | ||
# No quantity here even though it is set on option level in the input | ||
values: [SelectedBundleOptionValue!]! | ||
sort_order: Int! | ||
} | ||
|
||
type SelectedBundleOptionValue { | ||
id: Int! | ||
label: String! | ||
quantity: Float! # Quantity is displayed on option value level, while is set on option level | ||
price: CartItemSelectedOptionValuePrice! | ||
sort_order: Int! | ||
} | ||
|
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.
please add information
<extension_attributes for="Magento\Quote\Api\Data\PaymentInterface"> <!-- This is needed to provide type hint to serializer --> <attribute code="agreement_ids" type="string[]" /> </extension_attributes>