Skip to content

Commit d94914d

Browse files
committed
Implement configuration updates, dynamic height sizing, applePayLabel, widen iOS support, refactor context providers, derive checkout kit config from app config
1 parent ea60daa commit d94914d

16 files changed

Lines changed: 563 additions & 304 deletions

modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ Pod::Spec.new do |s|
2020
s.source_files = "ios/*.{h,m,mm,swift}"
2121

2222
s.dependency "React-Core"
23-
s.dependency "ShopifyCheckoutSheetKit", "~> 3.4.0-rc.2"
24-
s.dependency "ShopifyCheckoutSheetKit/AcceleratedCheckouts", "~> 3.4.0-rc.2"
23+
s.dependency "ShopifyCheckoutSheetKit", "~> 3.4.0-rc.5"
24+
s.dependency "ShopifyCheckoutSheetKit/AcceleratedCheckouts", "~> 3.4.0-rc.5"
2525

2626
if fabric_enabled
2727
install_modules_dependencies(s)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
MIT License
3+
4+
Copyright 2023 - Present, Shopify Inc.
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
*/
23+
24+
import _PassKit_SwiftUI
25+
import Foundation
26+
import PassKit
27+
import SwiftUI
28+
29+
@available(iOS 16.0, *)
30+
extension PayWithApplePayButtonLabel {
31+
static func from(_ string: String?, fallback: PayWithApplePayButtonLabel = .plain) -> PayWithApplePayButtonLabel {
32+
guard let string else {
33+
return fallback
34+
}
35+
return map[string] ?? .plain
36+
}
37+
38+
init?(fromString string: String?) {
39+
guard let string,
40+
let label = Self.map[string]
41+
else {
42+
return nil
43+
}
44+
self = label
45+
}
46+
47+
private static let map: [String: PayWithApplePayButtonLabel] = [
48+
"addMoney": .addMoney,
49+
"book": .book,
50+
"buy": .buy,
51+
"checkout": .checkout,
52+
"continue": .continue,
53+
"contribute": .contribute,
54+
"donate": .donate,
55+
"inStore": .inStore,
56+
"order": .order,
57+
"plain": .plain,
58+
"reload": .reload,
59+
"rent": .rent,
60+
"setUp": .setUp,
61+
"subscribe": .subscribe,
62+
"support": .support,
63+
"tip": .tip,
64+
"topUp": .topUp
65+
]
66+
}
67+
68+
extension View {
69+
@ViewBuilder
70+
func conditionalEnvironmentObject(_ object: (some ObservableObject)?) -> some View {
71+
if let object {
72+
environmentObject(object)
73+
} else {
74+
self
75+
}
76+
}
77+
}

modules/@shopify/checkout-sheet-kit/ios/AcceleratedCheckoutButtons.swift

Lines changed: 73 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -30,33 +30,34 @@ import UIKit
3030

3131
// MARK: - AcceleratedCheckout Components
3232

33-
@available(iOS 17.0, *)
33+
@available(iOS 16.0, *)
3434
class AcceleratedCheckoutConfiguration {
3535
static let shared = AcceleratedCheckoutConfiguration()
3636
var configuration: ShopifyAcceleratedCheckouts.Configuration?
37+
var applePayConfiguration: ShopifyAcceleratedCheckouts.ApplePayConfiguration?
3738
var wallets: [Wallet] = [Wallet.shopPay, Wallet.applePay]
3839

39-
private init() {
40-
setupApplePay()
40+
var available: Bool {
41+
if #available(iOS 16.0, *) {
42+
return configuration != nil
43+
} else {
44+
return false
45+
}
4146
}
4247

43-
private func setupApplePay() {
44-
// Configure Apple Pay environment
45-
// This should be called during app initialization
46-
if let merchantID = Bundle.main.object(forInfoDictionaryKey: "ApplePayMerchantID") as? String {
47-
// Apple Pay is configured via merchant ID in Info.plist
48-
// The actual Apple Pay setup is handled by the ShopifyAcceleratedCheckouts framework
49-
print("✅ Apple Pay configured with Merchant ID: \(merchantID)")
48+
var applePayAvailable: Bool {
49+
if #available(iOS 16.0, *) {
50+
return applePayConfiguration != nil
5051
} else {
51-
print("⚠️ Apple Pay Merchant ID not found in Info.plist. Add 'ApplePayMerchantID' key to enable Apple Pay.")
52+
return false
5253
}
5354
}
5455
}
5556

5657
@objc(RCTAcceleratedCheckoutButtonsManager)
5758
class RCTAcceleratedCheckoutButtonsManager: RCTViewManager {
5859
override func view() -> UIView! {
59-
if #available(iOS 17.0, *) {
60+
if #available(iOS 16.0, *) {
6061
return RCTAcceleratedCheckoutButtonsView()
6162
}
6263

@@ -73,12 +74,16 @@ class RCTAcceleratedCheckoutButtonsManager: RCTViewManager {
7374
}
7475
}
7576

76-
@available(iOS 17.0, *)
77+
@available(iOS 16.0, *)
7778
class RCTAcceleratedCheckoutButtonsView: UIView {
7879
private var hostingController: UIHostingController<AnyView>?
7980
private var configuration: ShopifyAcceleratedCheckouts.Configuration?
8081
private weak var parentViewController: UIViewController?
8182

83+
@objc var onSizeChange: RCTDirectEventBlock?
84+
85+
// MARK: - Props
86+
8287
@objc var cartId: String? {
8388
didSet {
8489
updateView()
@@ -104,6 +109,16 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
104109
}
105110

106111
@objc var wallets: [String]? {
112+
didSet {
113+
let height = calculateRequiredHeight()
114+
onSizeChange?(["height": height])
115+
invalidateIntrinsicContentSize()
116+
setNeedsLayout()
117+
updateView()
118+
}
119+
}
120+
121+
@objc var applePayLabel: String? {
107122
didSet {
108123
updateView()
109124
}
@@ -128,6 +143,16 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
128143
setupView()
129144
}
130145

146+
override func layoutSubviews() {
147+
super.layoutSubviews()
148+
hostingController?.view.frame = bounds
149+
}
150+
151+
override var intrinsicContentSize: CGSize {
152+
let height = calculateRequiredHeight()
153+
return CGSize(width: UIView.noIntrinsicMetric, height: height)
154+
}
155+
131156
private func setupView() {
132157
// Configuration will be set via a static method from the main module
133158
configuration = AcceleratedCheckoutConfiguration.shared.configuration
@@ -146,6 +171,20 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
146171
name: Notification.Name("AcceleratedCheckoutConfigurationUpdated"),
147172
object: nil
148173
)
174+
175+
NotificationCenter.default.addObserver(
176+
self,
177+
selector: #selector(configurationUpdated),
178+
name: Notification.Name("CheckoutKitConfigurationUpdated"),
179+
object: nil
180+
)
181+
182+
// Fire initial size change event
183+
DispatchQueue.main.async { [weak self] in
184+
guard let self else { return }
185+
let height = self.calculateRequiredHeight()
186+
self.onSizeChange?(["height": height])
187+
}
149188
}
150189

151190
private func findViewController() -> UIViewController? {
@@ -167,8 +206,9 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
167206
private func createCartButtons(
168207
cartId: String,
169208
wallets: [Wallet],
209+
applePayLabel: PayWithApplePayButtonLabel?,
170210
config: ShopifyAcceleratedCheckouts.Configuration,
171-
applePayConfig: ShopifyAcceleratedCheckouts.ApplePayConfiguration
211+
applePayConfig: ShopifyAcceleratedCheckouts.ApplePayConfiguration?
172212
) -> some View {
173213
AcceleratedCheckoutButtons(cartID: cartId)
174214
.wallets(wallets)
@@ -190,17 +230,19 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
190230
.onWebPixelEvent { [weak self] event in
191231
self?.handleWebPixelEvent(event)
192232
}
233+
.applePayLabel(applePayLabel ?? .plain)
193234
.cornerRadius(CGFloat(cornerRadius.doubleValue))
194-
.environment(config)
195-
.environment(applePayConfig)
235+
.environmentObject(config)
236+
.conditionalEnvironmentObject(applePayConfig)
196237
}
197238

198239
private func createVariantButtons(
199240
variantId: String,
200241
quantity: Int,
201242
wallets: [Wallet],
243+
applePayLabel: PayWithApplePayButtonLabel?,
202244
config: ShopifyAcceleratedCheckouts.Configuration,
203-
applePayConfig: ShopifyAcceleratedCheckouts.ApplePayConfiguration
245+
applePayConfig: ShopifyAcceleratedCheckouts.ApplePayConfiguration?
204246
) -> some View {
205247
AcceleratedCheckoutButtons(variantID: variantId, quantity: quantity)
206248
.wallets(wallets)
@@ -222,9 +264,10 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
222264
.onWebPixelEvent { [weak self] event in
223265
self?.handleWebPixelEvent(event)
224266
}
267+
.applePayLabel(applePayLabel ?? .plain)
225268
.cornerRadius(CGFloat(cornerRadius.doubleValue))
226-
.environment(config)
227-
.environment(applePayConfig)
269+
.environmentObject(config)
270+
.conditionalEnvironmentObject(applePayConfig)
228271
}
229272

230273
private func updateView() {
@@ -240,29 +283,24 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
240283
// Use wallets from props, or fallback to default
241284
let shopifyWallets = wallets.map(convertToShopifyWallets) ?? AcceleratedCheckoutConfiguration.shared.wallets
242285

243-
// Create Apple Pay configuration
244-
let applePayConfig = ShopifyAcceleratedCheckouts.ApplePayConfiguration(
245-
merchantIdentifier: Bundle.main.object(
246-
forInfoDictionaryKey: "ApplePayMerchantID") as? String ?? "merchant.com.shopify",
247-
contactFields: [.email, .phone]
248-
)
249-
250286
let swiftUIView: AnyView
251287

252288
if let cartId {
253289
swiftUIView = AnyView(createCartButtons(
254290
cartId: cartId,
255291
wallets: shopifyWallets,
292+
applePayLabel: PayWithApplePayButtonLabel.from(applePayLabel),
256293
config: config,
257-
applePayConfig: applePayConfig
294+
applePayConfig: AcceleratedCheckoutConfiguration.shared.applePayConfiguration
258295
))
259296
} else if let variantId {
260297
swiftUIView = AnyView(createVariantButtons(
261298
variantId: variantId,
262299
quantity: quantity.intValue,
263300
wallets: shopifyWallets,
301+
applePayLabel: PayWithApplePayButtonLabel.from(applePayLabel),
264302
config: config,
265-
applePayConfig: applePayConfig
303+
applePayConfig: AcceleratedCheckoutConfiguration.shared.applePayConfiguration
266304
))
267305
} else {
268306
// Empty view if no cart or variant ID is provided
@@ -331,13 +369,13 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
331369
onClickLink?(ShopifyEventSerialization.serialize(clickEvent: url))
332370
}
333371

334-
override func layoutSubviews() {
335-
super.layoutSubviews()
336-
hostingController?.view.frame = bounds
337-
}
372+
private func calculateRequiredHeight() -> CGFloat {
373+
let shopifyWallets = wallets.map(convertToShopifyWallets) ?? AcceleratedCheckoutConfiguration.shared.wallets
374+
let numberOfWallets = max(shopifyWallets.count, 1)
375+
let buttonHeight: CGFloat = 48
376+
let gapHeight: CGFloat = 8
377+
let totalHeight = (CGFloat(numberOfWallets) * buttonHeight) + (CGFloat(numberOfWallets - 1) * gapHeight)
338378

339-
override var intrinsicContentSize: CGSize {
340-
// Provide a default size for the button
341-
return CGSize(width: UIView.noIntrinsicMetric, height: 50)
379+
return totalHeight
342380
}
343381
}

modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,23 @@ @interface RCT_EXTERN_MODULE (RCTShopifyCheckoutSheetKit, NSObject)
4848

4949
/// Configure AcceleratedCheckouts
5050
RCT_EXTERN_METHOD(
51-
configureAcceleratedCheckouts : (NSString *)
52-
storefrontDomain storefrontAccessToken : (NSString *)
53-
storefrontAccessToken customerEmail : (NSString *)
54-
customerEmail customerPhoneNumber : (NSString *)
55-
customerPhoneNumber resolve : (RCTPromiseResolveBlock)
51+
configureAcceleratedCheckouts : (
52+
NSString *)storefrontDomain storefrontAccessToken : (NSString *)
53+
storefrontAccessToken customerEmail : (
54+
NSString *)customerEmail customerPhoneNumber : (NSString *)
55+
customerPhoneNumber applePayMerchantIdentifier : (NSString *)
56+
applePayMerchantIdentifier applyPayContactFields : (NSArray *)
57+
applyPayContactFields resolve : (RCTPromiseResolveBlock)
5658
resolve reject : (RCTPromiseRejectBlock)reject);
5759

5860
/// Check if accelerated checkout is available
5961
RCT_EXTERN_METHOD(isAcceleratedCheckoutAvailable : (RCTPromiseResolveBlock)
6062
resolve reject : (RCTPromiseRejectBlock)reject);
6163

64+
/// Check if Apple Pay is available
65+
RCT_EXTERN_METHOD(isApplePayAvailable : (RCTPromiseResolveBlock)
66+
resolve reject : (RCTPromiseRejectBlock)reject);
67+
6268
@end
6369

6470
// AcceleratedCheckoutButtons View Manager
@@ -70,6 +76,7 @@ @interface RCT_EXTERN_MODULE (RCTAcceleratedCheckoutButtonsManager,
7076
RCT_EXPORT_VIEW_PROPERTY(quantity, NSNumber *)
7177
RCT_EXPORT_VIEW_PROPERTY(cornerRadius, NSNumber *)
7278
RCT_EXPORT_VIEW_PROPERTY(wallets, NSArray *)
79+
RCT_EXPORT_VIEW_PROPERTY(applePayLabel, NSString *)
7380
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
7481
RCT_EXPORT_VIEW_PROPERTY(onFail, RCTBubblingEventBlock)
7582
RCT_EXPORT_VIEW_PROPERTY(onComplete, RCTBubblingEventBlock)
@@ -78,5 +85,6 @@ @interface RCT_EXTERN_MODULE (RCTAcceleratedCheckoutButtonsManager,
7885
RCT_EXPORT_VIEW_PROPERTY(onShouldRecoverFromError, RCTDirectEventBlock)
7986
RCT_EXPORT_VIEW_PROPERTY(onWebPixelEvent, RCTBubblingEventBlock)
8087
RCT_EXPORT_VIEW_PROPERTY(onClickLink, RCTBubblingEventBlock)
88+
RCT_EXPORT_VIEW_PROPERTY(onSizeChange, RCTDirectEventBlock)
8189

8290
@end

0 commit comments

Comments
 (0)