diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/Clock.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/Clock.swift index 1856b7ed..a56780e0 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/Clock.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/Clock.swift @@ -24,7 +24,7 @@ import Foundation /// Protocol for abstracting time-based operations to enable testing -protocol Clock { +protocol Clock: Sendable { /// Sleep for the specified number of nanoseconds func sleep(nanoseconds: UInt64) async throws } diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/Extensions/Task.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/Extensions/Task.swift index 528550b3..7c872df3 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/Extensions/Task.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/Extensions/Task.swift @@ -41,7 +41,7 @@ func exponentialDelay(for attempt: Int = 1, with retryDelay: TimeInterval) -> UI return UInt64(delayInNanoseconds) } -extension Task where Failure == Error { +extension Task where Failure == Error, Success: Sendable { @discardableResult static func retrying( priority: TaskPriority? = nil, maxRetryCount: Int = 3, diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLClient.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLClient.swift index fbb68a26..1406b9f6 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLClient.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLClient.swift @@ -26,7 +26,7 @@ import ShopifyCheckoutKit /// A lightweight GraphQL client for the Storefront API without external dependencies @available(iOS 16.0, *) -class GraphQLClient { +final class GraphQLClient: Sendable { let url: URL private let headers: [String: String] private let session: URLSession @@ -53,19 +53,19 @@ class GraphQLClient { /// Execute a GraphQL query /// - Parameter operation: The GraphQL query operation /// - Returns: The decoded response - func query(_ operation: GraphQLRequest) async throws -> GraphQLResponse { + func query(_ operation: GraphQLRequest) async throws -> GraphQLResponse { return try await execute(operation: operation) } /// Execute a GraphQL mutation /// - Parameter operation: The GraphQL mutation operation /// - Returns: The decoded response - func mutate(_ operation: GraphQLRequest) async throws -> GraphQLResponse { + func mutate(_ operation: GraphQLRequest) async throws -> GraphQLResponse { return try await execute(operation: operation) } /// Execute a raw GraphQL request - private func execute( + private func execute( operation: GraphQLRequest ) async throws -> GraphQLResponse { let urlRequest = try getURLRequest( diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLRequest/GraphQLRequest+Directives.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLRequest/GraphQLRequest+Directives.swift index 7b7058d5..ce5037cf 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLRequest/GraphQLRequest+Directives.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLRequest/GraphQLRequest+Directives.swift @@ -26,7 +26,7 @@ import Foundation /// The country and language context for the API requests /// see: https://shopify.dev/changelog/storefront-api-incontext-directive-supports-languages @available(iOS 16.0, *) -struct InContextDirective { +struct InContextDirective: Sendable { let countryCode: CountryCode let languageCode: ShopifyLanguageCode diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLResponse.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLResponse.swift index 75c833f4..46dfb661 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLResponse.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLResponse.swift @@ -22,7 +22,7 @@ */ /// GraphQL response structure -struct GraphQLResponse: Decodable { +struct GraphQLResponse: Decodable, Sendable { let data: T? let errors: [GraphQLResponseError]? let extensions: [String: AnyCodable]? @@ -34,18 +34,18 @@ struct GraphQLResponse: Decodable { } /// GraphQL error from response -struct GraphQLResponseError: Decodable, Error { +struct GraphQLResponseError: Decodable, Error, Sendable { let message: String let path: [String]? let locations: [Location]? let extensions: Extensions? - struct Location: Decodable { + struct Location: Decodable, Sendable { let line: Int let column: Int } - struct Extensions: Decodable { + struct Extensions: Decodable, Sendable { let code: String? let field: [String]? let cost: Int? diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLScalars.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLScalars.swift index 3d31e99c..1d324297 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLScalars.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLScalars.swift @@ -26,7 +26,7 @@ import Foundation /// Custom scalar types for the Storefront API enum GraphQLScalars { /// Represents a globally unique identifier (GID) in the Storefront API - struct ID: Codable, Hashable, CustomStringConvertible { + struct ID: Codable, Sendable, Hashable, CustomStringConvertible { let rawValue: String init(_ rawValue: String) { @@ -54,13 +54,13 @@ enum GraphQLScalars { } /// Represents a monetary value with a decimal amount and currency code - struct Money: Codable, Hashable { + struct Money: Codable, Sendable, Hashable { let amount: Decimal let currencyCode: String } /// Represents an ISO 8601 encoded date-time string - struct DateTime: Codable, Hashable { + struct DateTime: Codable, Sendable, Hashable { let date: Date init(_ date: Date) { @@ -99,7 +99,7 @@ enum GraphQLScalars { } /// Represents an absolute URL - struct URL: Codable, Hashable { + struct URL: Codable, Sendable, Hashable { let url: Foundation.URL init(_ url: Foundation.URL) { @@ -130,7 +130,7 @@ enum GraphQLScalars { } /// Represents HTML content - struct HTML: Codable, Hashable { + struct HTML: Codable, Sendable, Hashable { let rawValue: String init(_ rawValue: String) { @@ -150,7 +150,7 @@ enum GraphQLScalars { } /// ISO 4217 currency codes -enum CurrencyCode: String, Codable, CaseIterable { +enum CurrencyCode: String, Codable, Sendable, CaseIterable { case aed = "AED" case afn = "AFN" case all = "ALL" @@ -309,7 +309,7 @@ enum CurrencyCode: String, Codable, CaseIterable { case zmw = "ZMW" } -enum LanguageCode: String, CaseIterable, Codable { +enum LanguageCode: String, CaseIterable, Codable, Sendable { /// Afrikaans case AF /// Akan @@ -604,7 +604,7 @@ enum LanguageCode: String, CaseIterable, Codable { /// If a territory doesn't have a country code value in the `CountryCode` enum, then it might be considered a subdivision /// of another country. For example, the territories associated with Spain are represented by the country code `ES`, /// and the territories associated with the United States of America are represented by the country code `US`. -enum CountryCode: String, CaseIterable, Codable { +enum CountryCode: String, CaseIterable, Codable, Sendable { /// Afghanistan case AF /// Ă…land Islands diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLTypes.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLTypes.swift index 176ee992..979cdc7b 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLTypes.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/GraphQLClient/GraphQLTypes.swift @@ -24,7 +24,7 @@ import Foundation /// GraphQL client errors -enum GraphQLError: LocalizedError { +enum GraphQLError: LocalizedError, Sendable { case networkError(String) case httpError(statusCode: Int, data: Data) case decodingError(Error) @@ -52,8 +52,8 @@ enum GraphQLError: LocalizedError { } /// Helper type for encoding/decoding Any values -struct AnyCodable: Codable { - let value: Any +struct AnyCodable: Codable, @unchecked Sendable { + nonisolated(unsafe) let value: Any init(_ value: Any) { self.value = value diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI+Mutations.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI+Mutations.swift index e3f922a7..fe941662 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI+Mutations.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI+Mutations.swift @@ -34,7 +34,7 @@ extension StorefrontAPI { /// - Returns: The created cart func cartCreate( with items: [GraphQLScalars.ID] = [], - customer: ShopifyAcceleratedCheckouts.Customer? = nil + customer: ShopifyAcceleratedCheckouts.CustomerIdentity? = nil ) async throws -> Cart { var input: [String: Any] = [ "lines": items.map { ["merchandiseId": $0.rawValue] } @@ -75,7 +75,7 @@ extension StorefrontAPI { return cart } - struct CartBuyerIdentityUpdateInput: Codable { + struct CartBuyerIdentityUpdateInput: Codable, Sendable { var email: String? var phoneNumber: String? var customerAccessToken: String? diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI+Queries.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI+Queries.swift index 8c264b55..a5bb5dbf 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI+Queries.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI+Queries.swift @@ -45,11 +45,12 @@ extension StorefrontAPI { /// Get shop information /// - Returns: Shop details func shop() async throws -> Shop { - try await QueryCache.shared.load( + let client = client + return try await QueryCache.shared.load( cacheKey: "shop", url: client.url, query: { - let response = try await self.client.query(Operations.getShop()) + let response = try await client.query(Operations.getShop()) guard let shop = response.data?.shop else { throw StorefrontAPI.Errors.payload(propertyName: "shop") } @@ -64,16 +65,16 @@ extension StorefrontAPI { actor QueryCache { static let shared = QueryCache() - private var cache: [String: Any] = [:] - private var inflightRequests: [String: Any] = [:] + private var cache: [String: any Sendable] = [:] + private var inflightRequests: [String: any Sendable] = [:] private init() {} /// Loads data with deduplication - multiple simultaneous calls will share the same request - func load( + func load( cacheKey: String, url: URL, - query: @escaping () async throws -> T + query: @Sendable @escaping () async throws -> T ) async throws -> T { let key = buildCacheKey(queryKey: cacheKey, url: url) @@ -103,7 +104,7 @@ actor QueryCache { } } - private func cache(_ result: some Any, for key: String) { + private func cache(_ result: some Sendable, for key: String) { cache[key] = result } diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI+Types.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI+Types.swift index 48b93395..fe9a30d1 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI+Types.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI+Types.swift @@ -41,7 +41,7 @@ extension StorefrontAPI { } /// Represents a cart in the Storefront API - struct Cart: Codable { + struct Cart: Codable, Sendable { let id: Types.ID let checkoutUrl: GraphQLScalars.URL let totalQuantity: Int @@ -55,31 +55,31 @@ extension StorefrontAPI { } /// Cart discount code - struct CartDiscountCode: Codable { + struct CartDiscountCode: Codable, Sendable { let code: String let applicable: Bool } /// Cart buyer identity - struct CartBuyerIdentity: Codable { + struct CartBuyerIdentity: Codable, Sendable { let email: String? let phone: String? let customer: CartCustomer? } /// Cart customer information - struct CartCustomer: Codable { + struct CartCustomer: Codable, Sendable { let email: String? let phone: String? } /// Cart delivery information - struct CartDelivery: Codable { + struct CartDelivery: Codable, Sendable { let addresses: [CartSelectableAddress] } /// Cart selectable address - struct CartSelectableAddress: Codable { + struct CartSelectableAddress: Codable, Sendable { let id: Types.ID let selected: Bool let address: CartDeliveryAddress? @@ -88,7 +88,7 @@ extension StorefrontAPI { /// Cart delivery address /// Note: This is a GraphQL response type that uses countryCode/provinceCode /// instead of country/province like the input Address type - struct CartDeliveryAddress: Codable { + struct CartDeliveryAddress: Codable, Sendable { let address1: String? let address2: String? let city: String? @@ -101,7 +101,7 @@ extension StorefrontAPI { } /// Cart cost breakdown - struct CartCost: Codable { + struct CartCost: Codable, Sendable { let totalAmount: MoneyV2 let subtotalAmount: MoneyV2? let totalTaxAmount: MoneyV2? @@ -109,7 +109,7 @@ extension StorefrontAPI { } /// Money representation - struct MoneyV2: Codable { + struct MoneyV2: Codable, Sendable { let amount: Decimal let currencyCode: String @@ -155,12 +155,12 @@ extension StorefrontAPI { // MARK: - Cart Lines /// Connection type for cart lines - struct BaseCartLineConnection: Codable { + struct BaseCartLineConnection: Codable, Sendable { let nodes: [BaseCartLine] } /// Cart discount allocation - enum CartDiscountAllocation: Codable { + enum CartDiscountAllocation: Codable, Sendable { case automatic(CartAutomaticDiscountAllocation) case code(CartCodeDiscountAllocation) case custom(CartCustomDiscountAllocation) @@ -169,7 +169,7 @@ extension StorefrontAPI { case __typename } - private enum TypeName: String, Codable { + private enum TypeName: String, Codable, Sendable { case cartAutomaticDiscountAllocation = "CartAutomaticDiscountAllocation" case cartCodeDiscountAllocation = "CartCodeDiscountAllocation" case cartCustomDiscountAllocation = "CartCustomDiscountAllocation" @@ -205,14 +205,14 @@ extension StorefrontAPI { } /// Automatic discount allocation - struct CartAutomaticDiscountAllocation: Codable { + struct CartAutomaticDiscountAllocation: Codable, Sendable { let discountApplication: CartDiscountApplication let discountedAmount: MoneyV2 let targetType: DiscountApplicationTargetType } /// Code discount allocation - struct CartCodeDiscountAllocation: Codable { + struct CartCodeDiscountAllocation: Codable, Sendable { let code: String let discountApplication: CartDiscountApplication let discountedAmount: MoneyV2 @@ -220,34 +220,34 @@ extension StorefrontAPI { } /// Custom discount allocation - struct CartCustomDiscountAllocation: Codable { + struct CartCustomDiscountAllocation: Codable, Sendable { let discountApplication: CartDiscountApplication let discountedAmount: MoneyV2 let targetType: DiscountApplicationTargetType } /// Cart discount application - struct CartDiscountApplication: Codable { + struct CartDiscountApplication: Codable, Sendable { let targetSelection: DiscountApplicationTargetSelection let targetType: DiscountApplicationTargetType let value: PricingValue } /// Discount application target selection - enum DiscountApplicationTargetSelection: String, Codable { + enum DiscountApplicationTargetSelection: String, Codable, Sendable { case all = "ALL" case entitled = "ENTITLED" case explicit = "EXPLICIT" } /// Discount application target type - enum DiscountApplicationTargetType: String, Codable { + enum DiscountApplicationTargetType: String, Codable, Sendable { case lineItem = "LINE_ITEM" case shippingLine = "SHIPPING_LINE" } /// Pricing value (union type for percentage or fixed amount) - enum PricingValue: Codable { + enum PricingValue: Codable, Sendable { case percentage(PricingPercentageValue) case fixedAmount(MoneyV2) @@ -255,7 +255,7 @@ extension StorefrontAPI { case __typename } - private enum TypeName: String, Codable { + private enum TypeName: String, Codable, Sendable { case pricingPercentageValue = "PricingPercentageValue" case moneyV2 = "MoneyV2" } @@ -285,12 +285,12 @@ extension StorefrontAPI { } /// Pricing percentage value - struct PricingPercentageValue: Codable { + struct PricingPercentageValue: Codable, Sendable { let percentage: Double } /// Base cart line - struct BaseCartLine: Codable { + struct BaseCartLine: Codable, Sendable { let id: Types.ID let quantity: Int let merchandise: ProductVariant? @@ -299,7 +299,7 @@ extension StorefrontAPI { } /// Cart line cost - struct CartLineCost: Codable { + struct CartLineCost: Codable, Sendable { let totalAmount: MoneyV2 let subtotalAmount: MoneyV2 } @@ -307,7 +307,7 @@ extension StorefrontAPI { // MARK: - Product Models /// Product variant - struct ProductVariant: Codable { + struct ProductVariant: Codable, Sendable { let id: Types.ID let title: String let price: MoneyV2 @@ -316,7 +316,7 @@ extension StorefrontAPI { } /// Product - struct Product: Codable { + struct Product: Codable, Sendable { let id: Types.ID? let title: String let vendor: String? @@ -325,24 +325,24 @@ extension StorefrontAPI { } /// Product variant connection - struct ProductVariantConnection: Codable { + struct ProductVariantConnection: Codable, Sendable { let nodes: [ProductVariant] } /// Product connection - struct ProductConnection: Codable { + struct ProductConnection: Codable, Sendable { let nodes: [Product] } /// Image - struct Image: Codable { + struct Image: Codable, Sendable { let url: GraphQLScalars.URL } // MARK: - Shop /// Shop information - struct Shop: Codable { + struct Shop: Codable, Sendable { let name: String let description: String? let primaryDomain: ShopDomain @@ -352,21 +352,21 @@ extension StorefrontAPI { } /// Shop domain - struct ShopDomain: Codable { + struct ShopDomain: Codable, Sendable { let host: String let sslEnabled: Bool let url: GraphQLScalars.URL } /// Shop payment settings - struct ShopPaymentSettings: Codable { + struct ShopPaymentSettings: Codable, Sendable { let supportedDigitalWallets: [String] let acceptedCardBrands: [CardBrand] let countryCode: String } /// Card brands supported by Shopify's payment system - enum CardBrand: String, Codable, CaseIterable { + enum CardBrand: String, Codable, Sendable, CaseIterable { case americanExpress = "AMERICAN_EXPRESS" case dinersClub = "DINERS_CLUB" case discover = "DISCOVER" @@ -378,12 +378,12 @@ extension StorefrontAPI { // MARK: - Delivery Groups /// Connection type for delivery groups - struct CartDeliveryGroupConnection: Codable { + struct CartDeliveryGroupConnection: Codable, Sendable { let nodes: [CartDeliveryGroup] } /// Cart delivery group - struct CartDeliveryGroup: Codable { + struct CartDeliveryGroup: Codable, Sendable { let id: Types.ID let groupType: CartDeliveryGroupType let deliveryOptions: [CartDeliveryOption] @@ -391,13 +391,13 @@ extension StorefrontAPI { } /// Cart delivery group type - enum CartDeliveryGroupType: String, Codable { + enum CartDeliveryGroupType: String, Codable, Sendable { case oneTimePurchase = "ONE_TIME_PURCHASE" case subscription = "SUBSCRIPTION" } /// Cart delivery option - struct CartDeliveryOption: Codable { + struct CartDeliveryOption: Codable, Sendable { let handle: String let title: String let code: String? @@ -407,7 +407,7 @@ extension StorefrontAPI { } /// Delivery method type - enum DeliveryMethodType: String, Codable { + enum DeliveryMethodType: String, Codable, Sendable { case local = "LOCAL" case none = "NONE" case pickupPoint = "PICKUP_POINT" @@ -419,14 +419,14 @@ extension StorefrontAPI { // MARK: - User Errors /// Cart user error - struct CartUserError: Codable, Error { + struct CartUserError: Codable, Sendable, Error { let code: CartErrorCode? let message: String let field: [String]? } /// Cart error codes - enum CartErrorCode: String, Codable { + enum CartErrorCode: String, Codable, Sendable { case addressFieldContainsEmojis = "ADDRESS_FIELD_CONTAINS_EMOJIS" case addressFieldContainsHtmlTags = "ADDRESS_FIELD_CONTAINS_HTML_TAGS" case addressFieldContainsUrl = "ADDRESS_FIELD_CONTAINS_URL" @@ -499,7 +499,7 @@ extension StorefrontAPI { // MARK: - Mutation Payloads - struct CartPayload: Codable { + struct CartPayload: Codable, Sendable { let cart: Cart? let userErrors: [CartUserError] } @@ -526,13 +526,13 @@ extension StorefrontAPI { typealias CartRemovePersonalDataPayload = CartPayload /// Cart prepare for completion payload - struct CartPrepareForCompletionPayload: Codable { + struct CartPrepareForCompletionPayload: Codable, Sendable { let result: CartPrepareForCompletionResult? let userErrors: [CartUserError] } /// Cart prepare for completion result (union type) - enum CartPrepareForCompletionResult: Codable { + enum CartPrepareForCompletionResult: Codable, Sendable { case ready(CartStatusReady) case throttled(CartThrottled) case notReady(CartStatusNotReady) @@ -541,7 +541,7 @@ extension StorefrontAPI { case __typename } - private enum TypeName: String, Codable { + private enum TypeName: String, Codable, Sendable { case cartStatusReady = "CartStatusReady" case cartThrottled = "CartThrottled" case cartStatusNotReady = "CartStatusNotReady" @@ -577,30 +577,30 @@ extension StorefrontAPI { } /// Cart status ready - struct CartStatusReady: Codable { + struct CartStatusReady: Codable, Sendable { let cart: Cart? let checkoutURL: GraphQLScalars.URL? } /// Cart throttled - struct CartThrottled: Codable { + struct CartThrottled: Codable, Sendable { let pollAfter: GraphQLScalars.DateTime } /// Cart status not ready - struct CartStatusNotReady: Codable { + struct CartStatusNotReady: Codable, Sendable { let cart: Cart? let errors: [CartCompletionError] } /// Cart submit for completion payload - struct CartSubmitForCompletionPayload: Codable { + struct CartSubmitForCompletionPayload: Codable, Sendable { let result: CartSubmitForCompletionResult? let userErrors: [CartUserError] } /// Cart submit for completion result (union type) - enum CartSubmitForCompletionResult: Codable { + enum CartSubmitForCompletionResult: Codable, Sendable { case success(SubmitSuccess) case failed(SubmitFailed) case alreadyAccepted(SubmitAlreadyAccepted) @@ -610,7 +610,7 @@ extension StorefrontAPI { case __typename } - private enum TypeName: String, Codable { + private enum TypeName: String, Codable, Sendable { case submitSuccess = "SubmitSuccess" case submitFailed = "SubmitFailed" case submitAlreadyAccepted = "SubmitAlreadyAccepted" @@ -652,28 +652,28 @@ extension StorefrontAPI { } /// Submit success - struct SubmitSuccess: Codable { + struct SubmitSuccess: Codable, Sendable { let redirectUrl: GraphQLScalars.URL } /// Submit failed - struct SubmitFailed: Codable { + struct SubmitFailed: Codable, Sendable { let checkoutUrl: GraphQLScalars.URL? let errors: [CartCompletionError] } /// Submit already accepted - struct SubmitAlreadyAccepted: Codable { + struct SubmitAlreadyAccepted: Codable, Sendable { let attemptId: String } /// Submit throttled - struct SubmitThrottled: Codable { + struct SubmitThrottled: Codable, Sendable { let pollAfter: GraphQLScalars.DateTime } /// Cart completion error (used by both cartPrepareForCompletion and cartSubmitForCompletion) - struct CartCompletionError: Codable { + struct CartCompletionError: Codable, Sendable { let code: CartCompletionErrorCode let rawCode: String let message: String @@ -697,7 +697,7 @@ extension StorefrontAPI { } /// Error codes for cart completion (shared by prepare and submit stages) - enum CartCompletionErrorCode: String, Codable { + enum CartCompletionErrorCode: String, Codable, Sendable { // Buyer identity errors case buyerIdentityEmailIsInvalid = "BUYER_IDENTITY_EMAIL_IS_INVALID" case buyerIdentityEmailRequired = "BUYER_IDENTITY_EMAIL_REQUIRED" @@ -826,24 +826,24 @@ extension StorefrontAPI { // MARK: - Query Response Wrappers /// Response wrapper for cart query - struct CartQueryResponse: Codable { + struct CartQueryResponse: Codable, Sendable { let cart: Cart? } /// Response wrapper for products query - struct ProductsQueryResponse: Codable { + struct ProductsQueryResponse: Codable, Sendable { let products: ProductConnection } /// Response wrapper for shop query - struct ShopQueryResponse: Codable { + struct ShopQueryResponse: Codable, Sendable { let shop: Shop } // MARK: - Input Types /// Unified address structure for input operations - struct Address { + struct Address: Sendable { let address1: String? let address2: String? let city: String? @@ -914,7 +914,7 @@ extension StorefrontAPI { typealias DeliveryAddress = Address /// Apple Pay payment data - struct ApplePayPayment { + struct ApplePayPayment: Sendable { let billingAddress: ApplePayBillingAddress let ephemeralPublicKey: String let publicKeyHash: String diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI.swift index 99dfd61d..9b49b309 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Internal/StorefrontAPI/StorefrontAPI.swift @@ -25,7 +25,7 @@ import Foundation /// High-level API for Storefront operations using the custom GraphQL client @available(iOS 16.0, *) -class StorefrontAPI: ObservableObject, StorefrontAPIProtocol { +final class StorefrontAPI: ObservableObject, StorefrontAPIProtocol { let client: GraphQLClient /// Initialize the Storefront API @@ -56,7 +56,7 @@ class StorefrontAPI: ObservableObject, StorefrontAPIProtocol { } @available(iOS 16.0, *) -protocol StorefrontAPIProtocol { +protocol StorefrontAPIProtocol: Sendable { // MARK: - Query Methods func cart(by id: GraphQLScalars.ID) async throws -> StorefrontAPI.Cart? @@ -65,7 +65,7 @@ protocol StorefrontAPIProtocol { // MARK: - Mutation Methods @discardableResult func cartCreate( - with items: [GraphQLScalars.ID], customer: ShopifyAcceleratedCheckouts.Customer? + with items: [GraphQLScalars.ID], customer: ShopifyAcceleratedCheckouts.CustomerIdentity? ) async throws -> StorefrontAPI.Cart @discardableResult func cartBuyerIdentityUpdate( diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/ShopifyAcceleratedCheckouts+Configuration.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/ShopifyAcceleratedCheckouts+Configuration.swift index 26faaecc..f3b75864 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/ShopifyAcceleratedCheckouts+Configuration.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/ShopifyAcceleratedCheckouts+Configuration.swift @@ -26,6 +26,30 @@ import SwiftUI @available(iOS 16.0, *) extension ShopifyAcceleratedCheckouts { + struct CustomerIdentity: Sendable { + let email: String? + let phoneNumber: String? + let customerAccessToken: String? + + init?(customer: Customer?) { + guard let customer else { return nil } + self.init( + email: customer.email, + phoneNumber: customer.phoneNumber, + customerAccessToken: customer.customerAccessToken + ) + } + + init?(email: String? = nil, phoneNumber: String? = nil, customerAccessToken: String? = nil) { + guard email != nil || phoneNumber != nil || customerAccessToken != nil else { + return nil + } + self.email = email + self.phoneNumber = phoneNumber + self.customerAccessToken = customerAccessToken + } + } + public class Configuration: ObservableObject, Copyable { /// The domain of the shop without the protocol. /// diff --git a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Wallets/WalletController.swift b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Wallets/WalletController.swift index 9dc98c2b..7f5cfb65 100644 --- a/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Wallets/WalletController.swift +++ b/platforms/swift/Sources/ShopifyAcceleratedCheckouts/Wallets/WalletController.swift @@ -47,9 +47,10 @@ class WalletController: ObservableObject { case let .variant(id, quantity): let items = Array(repeating: GraphQLScalars.ID(id), count: quantity) + let customer = ShopifyAcceleratedCheckouts.CustomerIdentity(customer: configuration.customer) return try await storefront.cartCreate( with: items, - customer: configuration.customer + customer: customer ) case .invariant: diff --git a/platforms/swift/Tests/ShopifyAcceleratedCheckoutsTests/Internal/StorefrontAPI/StorefrontAPIMutationsTests.swift b/platforms/swift/Tests/ShopifyAcceleratedCheckoutsTests/Internal/StorefrontAPI/StorefrontAPIMutationsTests.swift index 62cefc26..6cd2d3e4 100644 --- a/platforms/swift/Tests/ShopifyAcceleratedCheckoutsTests/Internal/StorefrontAPI/StorefrontAPIMutationsTests.swift +++ b/platforms/swift/Tests/ShopifyAcceleratedCheckoutsTests/Internal/StorefrontAPI/StorefrontAPIMutationsTests.swift @@ -372,11 +372,11 @@ final class StorefrontAPIMutationsTests: XCTestCase { """ mockJSONResponse(json) - let customer = ShopifyAcceleratedCheckouts.Customer( + let customer = ShopifyAcceleratedCheckouts.CustomerIdentity( email: "customer@example.com", phoneNumber: "+1234567890", customerAccessToken: "test-token-123" - ) + )! let variantId = GraphQLScalars.ID("gid://shopify/ProductVariant/1") let cart = try await storefrontAPI.cartCreate(with: [variantId], customer: customer) diff --git a/platforms/swift/Tests/ShopifyAcceleratedCheckoutsTests/TestHelpers.swift b/platforms/swift/Tests/ShopifyAcceleratedCheckoutsTests/TestHelpers.swift index 437eb3f7..1dc688ae 100644 --- a/platforms/swift/Tests/ShopifyAcceleratedCheckoutsTests/TestHelpers.swift +++ b/platforms/swift/Tests/ShopifyAcceleratedCheckoutsTests/TestHelpers.swift @@ -307,7 +307,7 @@ class MockStorefrontAPI: StorefrontAPIProtocol { fatalError("shop() not implemented in test. Override this method in your test class.") } - func cartCreate(with _: [GraphQLScalars.ID], customer _: ShopifyAcceleratedCheckouts.Customer?) + func cartCreate(with _: [GraphQLScalars.ID], customer _: ShopifyAcceleratedCheckouts.CustomerIdentity?) async throws -> StorefrontAPI.Cart { fatalError( @@ -394,7 +394,7 @@ class TestStorefrontAPI: MockStorefrontAPI { } var cartCreateResult: Result? - override func cartCreate(with _: [GraphQLScalars.ID], customer _: ShopifyAcceleratedCheckouts.Customer?) async throws -> StorefrontAPI.Cart { + override func cartCreate(with _: [GraphQLScalars.ID], customer _: ShopifyAcceleratedCheckouts.CustomerIdentity?) async throws -> StorefrontAPI.Cart { guard let result = cartCreateResult else { fatalError("cartCreateResult not configured for TestStorefrontAPI") }