From c35bba3cec9a097d039b7552920a7b359ac457e5 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Tue, 11 Nov 2025 11:03:53 +0000 Subject: [PATCH 1/4] feat: add color modifier to AcceleratedChekcoutButtons --- .../MobileBuyIntegration/Views/CartView.swift | 4 ++++ .../Wallets/AcceleratedCheckoutButtons.swift | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/CartView.swift b/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/CartView.swift index fa10a6ec0..3e48b9af9 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/CartView.swift +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/CartView.swift @@ -62,6 +62,10 @@ struct CartView: View { .onCancel { print("Accelerated checkout cancelled") } + .applePayColor(.white) + // .applePayColor(.black) + // .applePayColor(.automatic) + // .applePayColor(.whiteOutline) .environmentObject(appConfiguration.acceleratedCheckoutsStorefrontConfig) .environmentObject(appConfiguration.acceleratedCheckoutsApplePayConfig) } diff --git a/Sources/ShopifyAcceleratedCheckouts/Wallets/AcceleratedCheckoutButtons.swift b/Sources/ShopifyAcceleratedCheckouts/Wallets/AcceleratedCheckoutButtons.swift index 98352bcc4..c573cb5dc 100644 --- a/Sources/ShopifyAcceleratedCheckouts/Wallets/AcceleratedCheckoutButtons.swift +++ b/Sources/ShopifyAcceleratedCheckouts/Wallets/AcceleratedCheckoutButtons.swift @@ -50,6 +50,7 @@ public struct AcceleratedCheckoutButtons: View { /// The Apple Pay button label style private var applePayLabel: PayWithApplePayButtonLabel = .plain + private var applePayColor: PayWithApplePayButtonStyle = .automatic @State private var shopSettings: ShopSettings? @State private var currentRenderState: RenderState = .loading { @@ -137,6 +138,12 @@ public struct AcceleratedCheckoutButtons: View { @available(iOS 16.0, *) extension AcceleratedCheckoutButtons { + public func applePayColor(_ color: PayWithApplePayButtonStyle) -> AcceleratedCheckoutButtons { + var view = self + view.applePayColor = color + return view + } + public func applePayLabel(_ label: PayWithApplePayButtonLabel) -> AcceleratedCheckoutButtons { var view = self view.applePayLabel = label From 8d735002f6003a2c4f1f06fe3cd1c64adc4b9677 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Tue, 11 Nov 2025 11:58:06 +0000 Subject: [PATCH 2/4] feat: wiring up color --- .../MobileBuyIntegration/Views/CartView.swift | 9 ++- .../Views/Components/ButtonSet.swift | 1 + .../Wallets/AcceleratedCheckoutButtons.swift | 14 ++-- .../Wallets/ApplePay/ApplePayButton.swift | 67 ++++++++++++++----- 4 files changed, 66 insertions(+), 25 deletions(-) diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/CartView.swift b/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/CartView.swift index 3e48b9af9..1daef8650 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/CartView.swift +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/CartView.swift @@ -50,7 +50,7 @@ struct CartView: View { VStack(spacing: DesignSystem.buttonSpacing) { if let cartId = cartManager.cart?.id { - AcceleratedCheckoutButtons(cartID: cartId) + AcceleratedCheckoutButtons(cartID: cartId, applePayStyle: .whiteOutline) .wallets([.shopPay, .applePay]) .cornerRadius(DesignSystem.cornerRadius) .onComplete { _ in @@ -62,10 +62,9 @@ struct CartView: View { .onCancel { print("Accelerated checkout cancelled") } - .applePayColor(.white) - // .applePayColor(.black) - // .applePayColor(.automatic) - // .applePayColor(.whiteOutline) + // .applePayStyle(.black) + // .applePayStyle(.automatic) + // .applePayStyle(.whiteOutline) .environmentObject(appConfiguration.acceleratedCheckoutsStorefrontConfig) .environmentObject(appConfiguration.acceleratedCheckoutsApplePayConfig) } diff --git a/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Views/Components/ButtonSet.swift b/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Views/Components/ButtonSet.swift index b4172fab2..fd32788b1 100644 --- a/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Views/Components/ButtonSet.swift +++ b/Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp/Views/Components/ButtonSet.swift @@ -88,6 +88,7 @@ struct ButtonSet: View { variantID: productVariant.id, quantity: firstVariantQuantity ) + .applePayStyle(.whiteOutline) .applePayLabel(.buy) .cornerRadius(24) .wallets([.applePay, .shopPay]) diff --git a/Sources/ShopifyAcceleratedCheckouts/Wallets/AcceleratedCheckoutButtons.swift b/Sources/ShopifyAcceleratedCheckouts/Wallets/AcceleratedCheckoutButtons.swift index c573cb5dc..a8de85962 100644 --- a/Sources/ShopifyAcceleratedCheckouts/Wallets/AcceleratedCheckoutButtons.swift +++ b/Sources/ShopifyAcceleratedCheckouts/Wallets/AcceleratedCheckoutButtons.swift @@ -50,7 +50,7 @@ public struct AcceleratedCheckoutButtons: View { /// The Apple Pay button label style private var applePayLabel: PayWithApplePayButtonLabel = .plain - private var applePayColor: PayWithApplePayButtonStyle = .automatic + private var applePayStyle: PayWithApplePayButtonStyle = .automatic @State private var shopSettings: ShopSettings? @State private var currentRenderState: RenderState = .loading { @@ -70,6 +70,11 @@ public struct AcceleratedCheckoutButtons: View { _currentRenderState = State(initialValue: .error(reason: reason)) } } + + public init(cartID: String, applePayStyle: PayWithApplePayButtonStyle) { + self.init(cartID: cartID) + self.applePayStyle = applePayStyle + } /// Initializes an Apple Pay button with a variant ID /// - Parameters: @@ -94,7 +99,8 @@ public struct AcceleratedCheckoutButtons: View { ApplePayButton( identifier: identifier, eventHandlers: eventHandlers, - cornerRadius: cornerRadius + cornerRadius: cornerRadius, + payWithApplePayButtonStyle: applePayStyle ) .label(applePayLabel) case .shopPay: @@ -138,9 +144,9 @@ public struct AcceleratedCheckoutButtons: View { @available(iOS 16.0, *) extension AcceleratedCheckoutButtons { - public func applePayColor(_ color: PayWithApplePayButtonStyle) -> AcceleratedCheckoutButtons { + public func applePayStyle(_ color: PayWithApplePayButtonStyle) -> AcceleratedCheckoutButtons { var view = self - view.applePayColor = color + view.applePayStyle = color return view } diff --git a/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButton.swift b/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButton.swift index 265cc16fa..f84159cd7 100644 --- a/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButton.swift +++ b/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButton.swift @@ -52,14 +52,18 @@ struct ApplePayButton: View { /// The corner radius for the button private let cornerRadius: CGFloat? + private let payWithApplePayButtonStyle: PayWithApplePayButtonStyle + public init( identifier: CheckoutIdentifier, eventHandlers: EventHandlers = EventHandlers(), - cornerRadius: CGFloat? + cornerRadius: CGFloat?, + payWithApplePayButtonStyle: PayWithApplePayButtonStyle ) { self.identifier = identifier.parse() self.eventHandlers = eventHandlers self.cornerRadius = cornerRadius + self.payWithApplePayButtonStyle = payWithApplePayButtonStyle } var body: some View { @@ -76,7 +80,8 @@ struct ApplePayButton: View { shopSettings: shopSettings ), eventHandlers: eventHandlers, - cornerRadius: cornerRadius + cornerRadius: cornerRadius, + payWithApplePayButtonStyle: payWithApplePayButtonStyle ) } } @@ -94,13 +99,15 @@ struct ApplePayButton: View { @available(macOS, unavailable) struct Internal_ApplePayButton: View { /// The Apple Pay button label style - private var label: PayWithApplePayButtonLabel = .plain + private let label: PayWithApplePayButtonLabel /// The view controller for the Apple Pay button - private var controller: ApplePayViewController + private let controller: ApplePayViewController /// The corner radius for the button private let cornerRadius: CGFloat? + + private let payWithApplePayButtonStyle: PayWithApplePayButtonStyle /// Initializes an Apple Pay button /// - Parameters: @@ -113,9 +120,10 @@ struct Internal_ApplePayButton: View { label: PayWithApplePayButtonLabel, configuration: ApplePayConfigurationWrapper, eventHandlers: EventHandlers = EventHandlers(), - cornerRadius: CGFloat? + cornerRadius: CGFloat?, + payWithApplePayButtonStyle: PayWithApplePayButtonStyle ) { - controller = ApplePayViewController( + self.controller = ApplePayViewController( identifier: identifier, configuration: configuration ) @@ -127,18 +135,45 @@ struct Internal_ApplePayButton: View { controller.onShouldRecoverFromError = eventHandlers.shouldRecoverFromError controller.onCheckoutClickLink = eventHandlers.checkoutDidClickLink controller.onCheckoutWebPixelEvent = eventHandlers.checkoutDidEmitWebPixelEvent + self.payWithApplePayButtonStyle = payWithApplePayButtonStyle + + } + + @ViewBuilder + private var applePayButton: some View { + if payWithApplePayButtonStyle == .black { + PayWithApplePayButton( + label, + action: { Task { await controller.onPress() } }, + fallback: { Text("errors.applePay.unsupported".localizedString) } + ) + .payWithApplePayButtonStyle(.black) + } else if payWithApplePayButtonStyle == .white { + PayWithApplePayButton( + label, + action: { Task { await controller.onPress() } }, + fallback: { Text("errors.applePay.unsupported".localizedString) } + ) + .payWithApplePayButtonStyle(.white) + } else if payWithApplePayButtonStyle == .whiteOutline { + PayWithApplePayButton( + label, + action: { Task { await controller.onPress() } }, + fallback: { Text("errors.applePay.unsupported".localizedString) } + ) + .payWithApplePayButtonStyle(.whiteOutline) + } else { + PayWithApplePayButton( + label, + action: { Task { await controller.onPress() } }, + fallback: { Text("errors.applePay.unsupported".localizedString) } + ) + .payWithApplePayButtonStyle(.automatic) + } } var body: some View { - PayWithApplePayButton( - label, - action: { - Task { await controller.onPress() } - }, - fallback: { - Text("errors.applePay.unsupported".localizedString) - } - ) - .walletButtonStyle(cornerRadius: cornerRadius) + applePayButton + .walletButtonStyle(cornerRadius: cornerRadius) } } From 504d1ea549cbaabebcba49f2df071b81138a06b1 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Thu, 2 Apr 2026 14:09:14 +0100 Subject: [PATCH 3/4] feat: use PKPaymentButton UIViewRepresentable for Apple Pay style support Replace SwiftUI PayWithApplePayButton with UIKit PKPaymentButton wrapped in UIViewRepresentable. This fixes border rendering issues with whiteOutline style at constrained frame heights, as PKPaymentButton allows setting style and cornerRadius directly at init time. Also aligns applePayStyle with modifier-only API pattern (removes init overload) for consistency with applePayLabel, cornerRadius, and wallets. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Localizable.xcstrings | 6 + .../ViewControllers/CartViewController.swift | 4 + .../MobileBuyIntegration/Views/CartView.swift | 9 +- .../Views/ProductView.swift | 4 + .../Views/SettingsView.swift | 50 +++++++ .../Wallets/AcceleratedCheckoutButtons.swift | 7 +- .../Wallets/ApplePay/ApplePayButton.swift | 126 ++++++++++-------- .../ApplePayButtonRepresentable.swift | 49 +++++++ 8 files changed, 187 insertions(+), 68 deletions(-) create mode 100644 Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButtonRepresentable.swift diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration/Localizable.xcstrings b/Samples/MobileBuyIntegration/MobileBuyIntegration/Localizable.xcstrings index 94874b2b3..cb791599a 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration/Localizable.xcstrings +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration/Localizable.xcstrings @@ -33,6 +33,9 @@ }, "Adding..." : { + }, + "Apple Pay" : { + }, "Authentication" : { "comment" : "A section header in the settings view.", @@ -71,6 +74,9 @@ }, "Clear logs" : { + }, + "Configures the visual style of the Apple Pay button." : { + }, "Events" : { diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration/ViewControllers/CartViewController.swift b/Samples/MobileBuyIntegration/MobileBuyIntegration/ViewControllers/CartViewController.swift index 482027290..204beed38 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration/ViewControllers/CartViewController.swift +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration/ViewControllers/CartViewController.swift @@ -320,7 +320,11 @@ class CartViewController: UIViewController, UITableViewDelegate, UITableViewData private func setupAcceleratedCheckoutButtons() { guard let cartId = CartManager.shared.cart?.id else { return } + let savedStyle = UserDefaults.standard.string(forKey: AppStorageKeys.applePayStyle.rawValue) + .flatMap(ApplePayStyleOption.init(rawValue:)) ?? .automatic + let acceleratedCheckoutButtonsView = AcceleratedCheckoutButtons(cartID: cartId) + .applePayStyle(savedStyle.style) .wallets([.shopPay, .applePay]) .cornerRadius(10) .onComplete { _ in diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/CartView.swift b/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/CartView.swift index 1daef8650..c12d84b4a 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/CartView.swift +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/CartView.swift @@ -38,6 +38,9 @@ struct CartView: View { @ObservedObject var cartManager: CartManager = .shared @ObservedObject var config: AppConfiguration = appConfiguration + @AppStorage(AppStorageKeys.applePayStyle.rawValue) + var applePayStyle: ApplePayStyleOption = .automatic + var body: some View { if let lines = cartManager.cart?.lines.nodes { ZStack(alignment: .bottom) { @@ -50,7 +53,8 @@ struct CartView: View { VStack(spacing: DesignSystem.buttonSpacing) { if let cartId = cartManager.cart?.id { - AcceleratedCheckoutButtons(cartID: cartId, applePayStyle: .whiteOutline) + AcceleratedCheckoutButtons(cartID: cartId) + .applePayStyle(applePayStyle.style) .wallets([.shopPay, .applePay]) .cornerRadius(DesignSystem.cornerRadius) .onComplete { _ in @@ -62,9 +66,6 @@ struct CartView: View { .onCancel { print("Accelerated checkout cancelled") } - // .applePayStyle(.black) - // .applePayStyle(.automatic) - // .applePayStyle(.whiteOutline) .environmentObject(appConfiguration.acceleratedCheckoutsStorefrontConfig) .environmentObject(appConfiguration.acceleratedCheckoutsApplePayConfig) } diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/ProductView.swift b/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/ProductView.swift index fae0a22ba..80644e068 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/ProductView.swift +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/ProductView.swift @@ -40,6 +40,9 @@ struct ProductView: View { @State private var descriptionExpanded: Bool = false @State private var addedToCart: Bool = false + @AppStorage(AppStorageKeys.applePayStyle.rawValue) + var applePayStyle: ApplePayStyleOption = .automatic + init(product: Product) { _product = State(initialValue: product) } @@ -134,6 +137,7 @@ struct ProductView: View { if variant.availableForSale { AcceleratedCheckoutButtons(variantID: variant.id, quantity: 1) + .applePayStyle(applePayStyle.style) .wallets([.applePay]) .cornerRadius(DesignSystem.cornerRadius) .onFail { error in diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/SettingsView.swift b/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/SettingsView.swift index 302cc745e..9f13bab33 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/SettingsView.swift +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration/Views/SettingsView.swift @@ -22,6 +22,7 @@ */ import Combine +import PassKit @preconcurrency import ShopifyAcceleratedCheckouts @preconcurrency import ShopifyCheckoutSheetKit import SwiftUI @@ -30,6 +31,7 @@ enum AppStorageKeys: String { case acceleratedCheckoutsLogLevel case checkoutSheetKitLogLevel case buyerIdentityMode + case applePayStyle } struct SettingsView: View { @@ -51,6 +53,9 @@ struct SettingsView: View { } } + @AppStorage(AppStorageKeys.applePayStyle.rawValue) + var applePayStyle: ApplePayStyleOption = .automatic + @State private var preloadingEnabled = ShopifyCheckoutSheetKit.configuration.preloading.enabled @State private var logs: [String?] = LogReader.shared.readLogs() ?? [] @State private var selectedColorScheme = ShopifyCheckoutSheetKit.configuration.colorScheme @@ -120,6 +125,26 @@ struct SettingsView: View { } } + Section( + header: Text("Apple Pay"), + footer: Text("Configures the visual style of the Apple Pay button.") + ) { + ForEach(ApplePayStyleOption.allCases, id: \.self) { option in + HStack { + Text(option.title) + Spacer() + if option == applePayStyle { + Text("\u{2713}") + } + } + .background(Color.clear) + .contentShape(Rectangle()) + .onTapGesture { + applePayStyle = option + } + } + } + Section(header: Text("Logging")) { Picker( "Accelerated Checkouts", @@ -307,6 +332,31 @@ extension Configuration.ColorScheme { } } +enum ApplePayStyleOption: String, CaseIterable { + case automatic + case black + case white + case whiteOutline + + var title: String { + switch self { + case .automatic: return "Automatic" + case .black: return "Black" + case .white: return "White" + case .whiteOutline: return "White Outline" + } + } + + var style: PayWithApplePayButtonStyle { + switch self { + case .automatic: return .automatic + case .black: return .black + case .white: return .white + case .whiteOutline: return .whiteOutline + } + } +} + #Preview { SettingsView() } diff --git a/Sources/ShopifyAcceleratedCheckouts/Wallets/AcceleratedCheckoutButtons.swift b/Sources/ShopifyAcceleratedCheckouts/Wallets/AcceleratedCheckoutButtons.swift index a8de85962..ad014ae4d 100644 --- a/Sources/ShopifyAcceleratedCheckouts/Wallets/AcceleratedCheckoutButtons.swift +++ b/Sources/ShopifyAcceleratedCheckouts/Wallets/AcceleratedCheckoutButtons.swift @@ -70,11 +70,6 @@ public struct AcceleratedCheckoutButtons: View { _currentRenderState = State(initialValue: .error(reason: reason)) } } - - public init(cartID: String, applePayStyle: PayWithApplePayButtonStyle) { - self.init(cartID: cartID) - self.applePayStyle = applePayStyle - } /// Initializes an Apple Pay button with a variant ID /// - Parameters: @@ -100,7 +95,7 @@ public struct AcceleratedCheckoutButtons: View { identifier: identifier, eventHandlers: eventHandlers, cornerRadius: cornerRadius, - payWithApplePayButtonStyle: applePayStyle + style: applePayStyle ) .label(applePayLabel) case .shopPay: diff --git a/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButton.swift b/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButton.swift index f84159cd7..557c8c84b 100644 --- a/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButton.swift +++ b/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButton.swift @@ -49,21 +49,22 @@ struct ApplePayButton: View { /// The Apple Pay button label style private var label: PayWithApplePayButtonLabel = .plain + /// The Apple Pay button style + private var style: PayWithApplePayButtonStyle = .automatic + /// The corner radius for the button private let cornerRadius: CGFloat? - private let payWithApplePayButtonStyle: PayWithApplePayButtonStyle - - public init( + init( identifier: CheckoutIdentifier, eventHandlers: EventHandlers = EventHandlers(), cornerRadius: CGFloat?, - payWithApplePayButtonStyle: PayWithApplePayButtonStyle + style: PayWithApplePayButtonStyle = .automatic ) { self.identifier = identifier.parse() self.eventHandlers = eventHandlers self.cornerRadius = cornerRadius - self.payWithApplePayButtonStyle = payWithApplePayButtonStyle + self.style = style } var body: some View { @@ -74,19 +75,25 @@ struct ApplePayButton: View { Internal_ApplePayButton( identifier: identifier, label: label, + style: style, configuration: ApplePayConfigurationWrapper( common: configuration, applePay: applePayConfiguration, shopSettings: shopSettings ), eventHandlers: eventHandlers, - cornerRadius: cornerRadius, - payWithApplePayButtonStyle: payWithApplePayButtonStyle + cornerRadius: cornerRadius ) } } - public func label(_ label: PayWithApplePayButtonLabel) -> some View { + func applePayStyle(_ style: PayWithApplePayButtonStyle) -> some View { + var view = self + view.style = style + return view + } + + func label(_ label: PayWithApplePayButtonLabel) -> some View { var view = self view.label = label return view @@ -98,36 +105,25 @@ struct ApplePayButton: View { @available(iOS 16.0, *) @available(macOS, unavailable) struct Internal_ApplePayButton: View { - /// The Apple Pay button label style private let label: PayWithApplePayButtonLabel - - /// The view controller for the Apple Pay button + private let style: PayWithApplePayButtonStyle private let controller: ApplePayViewController - - /// The corner radius for the button private let cornerRadius: CGFloat? - - private let payWithApplePayButtonStyle: PayWithApplePayButtonStyle - - /// Initializes an Apple Pay button - /// - Parameters: - /// - identifier: The identifier to use for checkout - /// - label: The label to display on the Apple Pay button - /// - configuration: The configuration for Apple Pay - /// - eventHandlers: The event handlers for checkout events (defaults to EventHandlers()) + init( identifier: CheckoutIdentifier, label: PayWithApplePayButtonLabel, + style: PayWithApplePayButtonStyle, configuration: ApplePayConfigurationWrapper, eventHandlers: EventHandlers = EventHandlers(), - cornerRadius: CGFloat?, - payWithApplePayButtonStyle: PayWithApplePayButtonStyle + cornerRadius: CGFloat? ) { - self.controller = ApplePayViewController( + controller = ApplePayViewController( identifier: identifier, configuration: configuration ) self.label = label + self.style = style self.cornerRadius = cornerRadius controller.onCheckoutComplete = eventHandlers.checkoutDidComplete controller.onCheckoutFail = eventHandlers.checkoutDidFail @@ -135,45 +131,59 @@ struct Internal_ApplePayButton: View { controller.onShouldRecoverFromError = eventHandlers.shouldRecoverFromError controller.onCheckoutClickLink = eventHandlers.checkoutDidClickLink controller.onCheckoutWebPixelEvent = eventHandlers.checkoutDidEmitWebPixelEvent - self.payWithApplePayButtonStyle = payWithApplePayButtonStyle - } - @ViewBuilder - private var applePayButton: some View { - if payWithApplePayButtonStyle == .black { - PayWithApplePayButton( - label, - action: { Task { await controller.onPress() } }, - fallback: { Text("errors.applePay.unsupported".localizedString) } - ) - .payWithApplePayButtonStyle(.black) - } else if payWithApplePayButtonStyle == .white { - PayWithApplePayButton( - label, - action: { Task { await controller.onPress() } }, - fallback: { Text("errors.applePay.unsupported".localizedString) } - ) - .payWithApplePayButtonStyle(.white) - } else if payWithApplePayButtonStyle == .whiteOutline { - PayWithApplePayButton( - label, - action: { Task { await controller.onPress() } }, - fallback: { Text("errors.applePay.unsupported".localizedString) } + var body: some View { + if PKPaymentAuthorizationController.canMakePayments() { + ApplePayButtonRepresentable( + buttonType: label.pkPaymentButtonType, + buttonStyle: style.pkPaymentButtonStyle, + cornerRadius: cornerRadius ?? 8, + action: { Task { await controller.onPress() } } ) - .payWithApplePayButtonStyle(.whiteOutline) + .id(style.pkPaymentButtonStyle.rawValue) + .frame(height: 48) } else { - PayWithApplePayButton( - label, - action: { Task { await controller.onPress() } }, - fallback: { Text("errors.applePay.unsupported".localizedString) } - ) - .payWithApplePayButtonStyle(.automatic) + Text("errors.applePay.unsupported".localizedString) } } +} - var body: some View { - applePayButton - .walletButtonStyle(cornerRadius: cornerRadius) +// MARK: - Type Conversions + +@available(iOS 16.0, *) +extension PayWithApplePayButtonStyle { + var pkPaymentButtonStyle: PKPaymentButtonStyle { + switch self { + case .black: .black + case .white: .white + case .whiteOutline: .whiteOutline + case .automatic: .automatic + default: .automatic + } + } +} + +@available(iOS 16.0, *) +extension PayWithApplePayButtonLabel { + var pkPaymentButtonType: PKPaymentButtonType { + switch self { + case .buy: .buy + case .setUp: .setUp + case .inStore: .inStore + case .donate: .donate + case .checkout: .checkout + case .book: .book + case .subscribe: .subscribe + case .reload: .reload + case .addMoney: .addMoney + case .topUp: .topUp + case .order: .order + case .rent: .rent + case .support: .support + case .contribute: .contribute + case .tip: .tip + default: .plain + } } } diff --git a/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButtonRepresentable.swift b/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButtonRepresentable.swift new file mode 100644 index 000000000..c6d649955 --- /dev/null +++ b/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButtonRepresentable.swift @@ -0,0 +1,49 @@ +/* + MIT License + + Copyright 2023 - Present, Shopify Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import PassKit +import SwiftUI +import UIKit + +@available(iOS 16.0, *) +@MainActor +struct ApplePayButtonRepresentable: UIViewRepresentable { + typealias UIViewType = PKPaymentButton + typealias Coordinator = Void + + let buttonType: PKPaymentButtonType + let buttonStyle: PKPaymentButtonStyle + let cornerRadius: CGFloat + let action: @Sendable () -> Void + + func makeUIView(context _: Context) -> PKPaymentButton { + let button = PKPaymentButton(paymentButtonType: buttonType, paymentButtonStyle: buttonStyle) + button.cornerRadius = cornerRadius + button.addAction(UIAction { _ in action() }, for: .touchUpInside) + return button + } + + func updateUIView(_ button: PKPaymentButton, context _: Context) { + button.cornerRadius = cornerRadius + } +} From 8e1dfd90808c972c86292dba23352c7289e91cce Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Thu, 2 Apr 2026 14:09:14 +0100 Subject: [PATCH 4/4] feat: use PKPaymentButton UIViewRepresentable for Apple Pay style support Replace SwiftUI PayWithApplePayButton with UIKit PKPaymentButton wrapped in UIViewRepresentable. This fixes border rendering issues with whiteOutline style at constrained frame heights, as PKPaymentButton allows setting style and cornerRadius directly at init time. Also aligns applePayStyle with modifier-only API pattern (removes init overload) for consistency with applePayLabel, cornerRadius, and wallets. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/lint.yml | 2 +- Scripts/lint | 23 +++++++++++++++++++ .../ApplePayButtonRepresentable.swift | 6 ++--- dev.yml | 5 ---- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fb4713b8f..b011671cb 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -43,7 +43,7 @@ jobs: - name: Add Mint to PATH run: echo "${{ github.workspace }}/.mint/bin" >> "$GITHUB_PATH" - - run: ./Scripts/lint check --verbose + - run: ./Scripts/lint check --verbose --skip-pod check-license-headers: name: License Headers diff --git a/Scripts/lint b/Scripts/lint index 1d4dea31d..e9da9f7bb 100755 --- a/Scripts/lint +++ b/Scripts/lint @@ -108,3 +108,26 @@ if [ $FORMAT_STATUS -ne 0 ]; then print_linting_error "SwiftFormat" exit 1 fi + +# Run CocoaPods lint (check mode only, not applicable to fix) +# Skip with --skip-pod (e.g. in CI where pod lint runs as a separate job) +SKIP_POD=false +if [[ "$*" == *"--skip-pod"* ]]; then + SKIP_POD=true +fi + +if [[ "$MODE" == "check" && "$SKIP_POD" == "false" ]]; then + echo "" + echo "🔍 Running CocoaPods lint..." + bundle exec pod lib lint --allow-warnings + POD_STATUS=$? + + if [ $POD_STATUS -eq 0 ]; then + echo "✅ CocoaPods lint exit status: $POD_STATUS" + else + echo "❌ CocoaPods lint exit status: $POD_STATUS" + echo "❌ CocoaPods detected issues that need to be fixed." + echo "🔧 Run 'bundle exec pod lib lint --allow-warnings --verbose' for details" + exit 1 + fi +fi diff --git a/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButtonRepresentable.swift b/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButtonRepresentable.swift index c6d649955..2557d7a98 100644 --- a/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButtonRepresentable.swift +++ b/Sources/ShopifyAcceleratedCheckouts/Wallets/ApplePay/ApplePayButtonRepresentable.swift @@ -26,24 +26,22 @@ import SwiftUI import UIKit @available(iOS 16.0, *) -@MainActor struct ApplePayButtonRepresentable: UIViewRepresentable { typealias UIViewType = PKPaymentButton - typealias Coordinator = Void let buttonType: PKPaymentButtonType let buttonStyle: PKPaymentButtonStyle let cornerRadius: CGFloat let action: @Sendable () -> Void - func makeUIView(context _: Context) -> PKPaymentButton { + func makeUIView(context _: UIViewRepresentableContext) -> PKPaymentButton { let button = PKPaymentButton(paymentButtonType: buttonType, paymentButtonStyle: buttonStyle) button.cornerRadius = cornerRadius button.addAction(UIAction { _ in action() }, for: .touchUpInside) return button } - func updateUIView(_ button: PKPaymentButton, context _: Context) { + func updateUIView(_ button: PKPaymentButton, context _: UIViewRepresentableContext) { button.cornerRadius = cornerRadius } } diff --git a/dev.yml b/dev.yml index 09e189fed..7532ee870 100644 --- a/dev.yml +++ b/dev.yml @@ -38,11 +38,6 @@ commands: aliases: [style] desc: Check format and lint issues across all Swift files using SwiftLint and SwiftFormat run: scripts/lint - subcommands: - pod: - aliases: [] - desc: Check the build for cocoapods deployement - run: bundle exec pod lib lint --allow-warnings fix: desc: Automatically fix format and lint issues where possible