-
Notifications
You must be signed in to change notification settings - Fork 24
fix: remove tax fields from cart queries unless its prepare for completion #280
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
Changes from all commits
4d9d99a
cfffb45
086d7d3
c6822f8
bd0dd6f
f9281af
332608a
25543dc
3f8b629
04b6f8e
55c0b98
cea7874
5bf3035
b82f649
4023855
4a22f56
6efdb8b
91ad2c3
0f64d27
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 |
---|---|---|
|
@@ -103,26 +103,16 @@ extension ApplePayHandler: PKPaymentAuthorizationControllerDelegate { | |
print(CartManager.Errors.invariant(message: "Shipping method identifier is nil")) | ||
return PKPaymentRequestShippingMethodUpdate( | ||
paymentSummaryItems: PassKitFactory.shared.createPaymentSummaryItems( | ||
cart: CartManager.shared.cart, | ||
shippingMethod: nil | ||
) | ||
) | ||
} | ||
|
||
do { | ||
_ = try await CartManager.shared.performCartSelectedDeliveryOptionsUpdate( | ||
try await CartManager.shared.performCartSelectedDeliveryOptionsUpdate( | ||
deliveryOptionHandle: identifier | ||
) | ||
|
||
let cart = try await CartManager.shared.performCartPrepareForCompletion() | ||
|
||
let paymentRequestShippingContactUpdate = PKPaymentRequestShippingMethodUpdate( | ||
paymentSummaryItems: PassKitFactory.shared.createPaymentSummaryItems( | ||
cart: cart, | ||
shippingMethod: shippingMethod | ||
) | ||
) | ||
return paymentRequestShippingContactUpdate | ||
} catch { | ||
print( | ||
CartManager.Errors.apiErrors( | ||
|
@@ -131,10 +121,19 @@ extension ApplePayHandler: PKPaymentAuthorizationControllerDelegate { | |
"Check response from cartSelectedDeliveryOptionsUpdate or cartPrepareForCompletion \(error)" | ||
) | ||
) | ||
} | ||
|
||
do { | ||
try await CartManager.shared.performCartPrepareForCompletion() | ||
|
||
return PKPaymentRequestShippingMethodUpdate( | ||
paymentSummaryItems: PassKitFactory.shared.createPaymentSummaryItems( | ||
shippingMethod: shippingMethod | ||
) | ||
) | ||
} catch { | ||
return PKPaymentRequestShippingMethodUpdate( | ||
paymentSummaryItems: PassKitFactory.shared.createPaymentSummaryItems( | ||
cart: CartManager.shared.cart, | ||
shippingMethod: nil | ||
) | ||
) | ||
|
@@ -146,7 +145,7 @@ extension ApplePayHandler: PKPaymentAuthorizationControllerDelegate { | |
didSelectShippingContact contact: PKContact | ||
) async -> PKPaymentRequestShippingContactUpdate { | ||
do { | ||
_ = try await CartManager.shared.performBuyerIdentityUpdate( | ||
try await CartManager.shared.performCartBuyerIdentityUpdate( | ||
contact: contact, | ||
partial: true | ||
) | ||
|
@@ -155,12 +154,11 @@ extension ApplePayHandler: PKPaymentAuthorizationControllerDelegate { | |
firstDeliveryGroup: CartManager.shared.cart?.deliveryGroups.nodes.first | ||
) | ||
|
||
_ = try await CartManager.shared.performCartPrepareForCompletion() | ||
try await CartManager.shared.performCartPrepareForCompletion() | ||
|
||
return PKPaymentRequestShippingContactUpdate( | ||
errors: [], | ||
paymentSummaryItems: PassKitFactory.shared.createPaymentSummaryItems( | ||
cart: CartManager.shared.cart, | ||
shippingMethod: nil | ||
), | ||
shippingMethods: shippingMethods | ||
|
@@ -170,7 +168,6 @@ extension ApplePayHandler: PKPaymentAuthorizationControllerDelegate { | |
return PKPaymentRequestShippingContactUpdate( | ||
errors: [error], | ||
paymentSummaryItems: PassKitFactory.shared.createPaymentSummaryItems( | ||
cart: CartManager.shared.cart, | ||
shippingMethod: nil | ||
), | ||
shippingMethods: [] | ||
|
@@ -185,29 +182,26 @@ extension ApplePayHandler: PKPaymentAuthorizationControllerDelegate { | |
/** | ||
* Apply validations that make sense for your business requirements | ||
*/ | ||
guard | ||
let shippingContact = payment.shippingContact, | ||
payment.shippingContact?.postalAddress?.isoCountryCode == "US" | ||
else { | ||
guard let shippingContact = payment.shippingContact else { | ||
paymentStatus = .failure | ||
return PassKitFactory.shared.createPKPaymentUSAdressError() | ||
} | ||
|
||
guard | ||
let emailAddress = shippingContact.emailAddress, | ||
emailAddress.isEmpty == false | ||
else { | ||
paymentStatus = .failure | ||
return PassKitFactory.shared.createPKPaymentEmailError() | ||
} | ||
|
||
/** | ||
* (Optional) If the user is a guest and you haven't set an email on buyerIdentity | ||
* update the buyerIdentity with the shippingContact.email | ||
*/ | ||
if appConfiguration.useVaultedState == false { | ||
/** | ||
* (Optional) If the user is a guest and you haven't set an email on buyerIdentity | ||
* update the buyerIdentity with the shippingContact.email | ||
*/ | ||
guard | ||
let emailAddress = shippingContact.emailAddress, | ||
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. email guard moved in to vaulted state check as we require it via apple payment so its guaranteed to be set outside of this flow |
||
emailAddress.isEmpty == false | ||
else { | ||
paymentStatus = .failure | ||
return PassKitFactory.shared.createPKPaymentEmailError() | ||
} | ||
|
||
do { | ||
_ = try await CartManager.shared.performBuyerIdentityUpdate( | ||
try await CartManager.shared.performCartBuyerIdentityUpdate( | ||
contact: shippingContact, | ||
partial: true | ||
) | ||
|
@@ -219,14 +213,17 @@ extension ApplePayHandler: PKPaymentAuthorizationControllerDelegate { | |
} | ||
|
||
do { | ||
_ = try await CartManager.shared.performCartPaymentUpdate(payment: payment) | ||
try await CartManager.shared.performCartPrepareForCompletion() | ||
sleep(1) | ||
try await CartManager.shared.performCartPaymentUpdate(payment: payment) | ||
} catch { | ||
print("[didAuthorizePayment][performCartPaymentUpdate][failure] \(error)") | ||
paymentStatus = .failure | ||
return PKPaymentAuthorizationResult(status: .failure, errors: [error]) | ||
} | ||
|
||
do { | ||
sleep(1) | ||
let response = try await CartManager.shared.performCartSubmitForCompletion() | ||
CartManager.shared.redirectUrl = response.redirectUrl | ||
paymentStatus = .success | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,16 @@ class CartManager: ObservableObject { | |
public var redirectUrl: URL? | ||
|
||
@Published var cart: Storefront.Cart? | ||
/** | ||
* Represents the cart's total tax amount (`cart.totalTaxAmount.amount`). | ||
* | ||
* This property is handled separately from the main cart object because: | ||
* 1. The BuySDK throws errors when accessing unrequested properties | ||
* 2. `totalTaxAmount` is deprecated in most cart operations (only available in `prepareForCompletion`) | ||
* | ||
* By isolating this property, we avoid SDK errors while maintaining access to tax data when needed. | ||
*/ | ||
@Published var totalTaxAmount: Decimal? | ||
@Published var isDirty: Bool = false | ||
|
||
// MARK: Initializers | ||
|
@@ -68,7 +78,7 @@ class CartManager: ObservableObject { | |
* Creates cart if no cart.id present, or adds line items to pre-existing cart | ||
* Non-idempotent - subsequent calls for existing cartLine items will increase quantity by 1 | ||
*/ | ||
func performCartLinesAdd(variant: GraphQL.ID) async throws -> Storefront.Cart { | ||
@discardableResult func performCartLinesAdd(variant: GraphQL.ID) async throws -> Storefront.Cart { | ||
guard let cartId = cart?.id else { | ||
return try await performCartCreate(items: [variant]) | ||
} | ||
|
@@ -80,7 +90,7 @@ class CartManager: ObservableObject { | |
) { | ||
$0.cartLinesAdd(cartId: cartId, lines: lines) { | ||
$0.cart { $0.cartManagerFragment() } | ||
.userErrors { $0.code().message() } | ||
.userErrors { $0.code().message() } | ||
} | ||
} | ||
|
||
|
@@ -151,14 +161,13 @@ class CartManager: ObservableObject { | |
} | ||
} | ||
|
||
func performBuyerIdentityUpdate( | ||
@discardableResult func performCartBuyerIdentityUpdate( | ||
contact: PKContact, | ||
partial _: Bool | ||
) async throws -> Storefront.Cart { | ||
guard let cartId = cart?.id else { | ||
throw Errors.invariant(message: "cart.id should be defined") | ||
} | ||
|
||
guard let address = contact.postalAddress else { | ||
throw Errors.invariant(message: "contact.postalAddress is nil") | ||
} | ||
|
@@ -252,7 +261,7 @@ class CartManager: ObservableObject { | |
} | ||
} | ||
|
||
func performCartSelectedDeliveryOptionsUpdate( | ||
@discardableResult func performCartSelectedDeliveryOptionsUpdate( | ||
deliveryOptionHandle: String | ||
) async throws -> Storefront.Cart { | ||
guard let cartId = cart?.id else { | ||
|
@@ -312,7 +321,7 @@ class CartManager: ObservableObject { | |
} | ||
} | ||
|
||
func performCartPaymentUpdate( | ||
@discardableResult func performCartPaymentUpdate( | ||
payment: PKPayment // REFACTOR: this method should just receive the decoded payment token | ||
) async throws -> Storefront.Cart { | ||
guard let cartId = cart?.id else { | ||
|
@@ -344,7 +353,7 @@ class CartManager: ObservableObject { | |
|
||
let mutation = Storefront.buildMutation(inContext: CartManager.ContextDirective) { | ||
$0.cartPaymentUpdate(cartId: cartId, payment: paymentInput) { | ||
$0.cart { $0.cartManagerFragment() } | ||
$0.cart { $0.cartPrepareForCompletionFragment() } | ||
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. this change adds tax pulling for the payment update which is still not deprecated |
||
.userErrors { | ||
$0.code().message() | ||
} | ||
|
@@ -376,7 +385,7 @@ class CartManager: ObservableObject { | |
} | ||
} | ||
|
||
func performCartPrepareForCompletion() async throws -> Storefront.Cart { | ||
@discardableResult func performCartPrepareForCompletion() async throws -> Storefront.Cart { | ||
guard let cartId = cart?.id else { | ||
throw Errors.invariant(message: "cart.id should be defined") | ||
} | ||
|
@@ -386,10 +395,10 @@ class CartManager: ObservableObject { | |
) { | ||
$0.cartPrepareForCompletion(cartId: cartId) { | ||
$0.result { | ||
$0.onCartStatusReady { $0.cart { $0.cartManagerFragment() } } | ||
$0.onCartStatusReady { $0.cart { $0.cartPrepareForCompletionFragment() } } | ||
$0.onCartThrottled { $0.pollAfter() } | ||
$0.onCartStatusNotReady { | ||
$0.cart { $0.cartManagerFragment() } | ||
$0.cart { $0.cartPrepareForCompletionFragment() } | ||
.errors { $0.code().message() } | ||
} | ||
}.userErrors { $0.code().message() } | ||
|
@@ -410,17 +419,27 @@ class CartManager: ObservableObject { | |
|
||
guard | ||
let result = payload.result as? Storefront.CartStatusReady, | ||
let cart = result.cart | ||
let cartWithResolvedPendingTerms = result.cart | ||
else { | ||
throw Errors.invariant( | ||
message: "CartPrepareForCompletionResult is not CartStatusReady") | ||
} | ||
|
||
DispatchQueue.main.async { | ||
self.cart = cart | ||
self.cart = cartWithResolvedPendingTerms | ||
/** | ||
* IMPORTANT: Special handling for `totalTaxAmount`: | ||
* - Deprecated field on cart, except from `cartPrepareForCompletion` mutation | ||
* - Not included in standard cart fragments used by other mutations | ||
* - Accessing `self.cart.cost.totalTaxAmount` will throw if cart was set by other mutations | ||
* - Store separately to preserve the value when cart is updated by subsequent mutations | ||
*/ | ||
if let tax = cartWithResolvedPendingTerms.cost.totalTaxAmount?.amount { | ||
self.totalTaxAmount = tax | ||
} | ||
} | ||
|
||
return cart | ||
return cartWithResolvedPendingTerms | ||
} catch { | ||
throw Errors.apiErrors(requestName: "cartSubmitForCompletion", message: "\(error)") | ||
} | ||
|
@@ -435,7 +454,11 @@ class CartManager: ObservableObject { | |
$0.cartSubmitForCompletion(cartId: cartId, attemptToken: UUID().uuidString) { | ||
$0.result { | ||
$0.onSubmitSuccess { $0.redirectUrl() } | ||
$0.onSubmitFailed { $0.checkoutUrl() } | ||
$0.onSubmitFailed { | ||
$0.checkoutUrl().errors { | ||
$0.code().message() | ||
} | ||
} | ||
$0.onSubmitAlreadyAccepted { $0.attemptId() } | ||
$0.onSubmitThrottled { $0.pollAfter() } | ||
} | ||
|
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.
this ensures prepare is called even if updating the shipping options fails, which happens regularly at the moment due to a 500 server error atm