Skip to content

Commit ecc4f95

Browse files
authored
Integrate Swift RC 6 (#298)
* Integrate Swift RC 6 * Add tests * Throw invalid wallets, log unexpected render states, use guard
1 parent f3b7ab3 commit ecc4f95

12 files changed

+274
-69
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 3.4.0-rc.1 - Aug 27, 2025
4+
5+
- Adds support for Accelerated Checkouts on iOS devices
6+
37
## 3.3.0 - Aug 12, 2025
48

59
- Upgrades Swift dependency to 3.3.0

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.5"
24-
s.dependency "ShopifyCheckoutSheetKit/AcceleratedCheckouts", "~> 3.4.0-rc.5"
23+
s.dependency "ShopifyCheckoutSheetKit", "~> 3.4.0-rc.6"
24+
s.dependency "ShopifyCheckoutSheetKit/AcceleratedCheckouts", "~> 3.4.0-rc.6"
2525

2626
if fabric_enabled
2727
install_modules_dependencies(s)

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

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ class AcceleratedCheckoutConfiguration {
3535
static let shared = AcceleratedCheckoutConfiguration()
3636
var configuration: ShopifyAcceleratedCheckouts.Configuration?
3737
var applePayConfiguration: ShopifyAcceleratedCheckouts.ApplePayConfiguration?
38-
var defaultWallets: [Wallet] = [.applePay, .shopPay]
3938

4039
var available: Bool {
4140
if #available(iOS 16.0, *) {
@@ -84,6 +83,7 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
8483
private var hostingController: UIHostingController<AnyView>?
8584
private var configuration: ShopifyAcceleratedCheckouts.Configuration?
8685
private weak var parentViewController: UIViewController?
86+
internal var instance: AcceleratedCheckoutButtons?
8787

8888
@objc var onSizeChange: RCTDirectEventBlock?
8989

@@ -132,9 +132,13 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
132132

133133
/// Compute the wallets to render based on the `wallets` prop.
134134
/// If `wallets` is provided and empty, render nothing. No fallback here; SDK provides defaults.
135-
private var walletsToRender: [Wallet] {
135+
private var shopifyWallets: [Wallet] {
136136
guard let providedWallets = wallets else { return [] }
137-
return convertToShopifyWallets(providedWallets)
137+
do {
138+
return try convertToShopifyWallets(providedWallets)
139+
} catch {
140+
return []
141+
}
138142
}
139143

140144
override init(frame: CGRect) {
@@ -244,17 +248,19 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
244248
}
245249

246250
private func updateView() {
251+
let walletsEmpty = wallets != nil && shopifyWallets.isEmpty
252+
247253
guard
248254
let config = configuration,
249-
let wallets, wallets != nil, wallets.count > 0,
250-
let checkoutIdentifierDictionary = checkoutIdentifier as? [String: Any]
255+
let checkoutIdentifierDictionary = checkoutIdentifier as? [String: Any],
256+
!walletsEmpty
251257
else {
252258
renderEmptyView()
253259
return
254260
}
255261

256262
/// Map wallets if provided; otherwise let the kit decide the defaults
257-
let shopifyWallets = convertToShopifyWallets(wallets)
263+
let shopifyWallets: [Wallet]? = wallets != nil ? shopifyWallets : nil
258264

259265
var buttons: AcceleratedCheckoutButtons
260266

@@ -308,6 +314,8 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
308314
// Ensure this view can also receive touch events
309315
isUserInteractionEnabled = true
310316

317+
instance = buttons
318+
311319
/// Fire size change event
312320
resizeWallets()
313321
}
@@ -360,25 +368,28 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
360368
}
361369

362370
private func renderEmptyView() {
371+
instance = nil
363372
hostingController?.rootView = AnyView(EmptyView())
364373
onSizeChange?(["height": 0])
365374
}
366375

376+
/// Cases for returning 0 height
377+
/// - No buttons instance available
378+
/// - Wallets is explicitly an empty array
379+
/// - OR wallets is provided and maps to empty
367380
private func calculateRequiredHeight() -> CGFloat {
368-
/// If wallets prop is explicitly provided and maps to empty, height is zero
369-
if wallets != nil, walletsToRender.isEmpty {
381+
guard
382+
let instance,
383+
wallets?.isEmpty != true,
384+
!(wallets != nil && shopifyWallets.isEmpty)
385+
else {
370386
return 0
371387
}
372388

373-
/// If wallets are provided and non-empty, use their count
374-
if wallets != nil, !walletsToRender.isEmpty {
375-
let numberOfWallets = max(walletsToRender.count, 1)
376-
let buttonHeight: CGFloat = 48
377-
let gapHeight: CGFloat = 8
378-
return (CGFloat(numberOfWallets) * buttonHeight) + (CGFloat(numberOfWallets - 1) * gapHeight)
379-
}
389+
let numberOfWallets = shopifyWallets.isEmpty
390+
? instance.wallets.count
391+
: max(shopifyWallets.count, 1)
380392

381-
let numberOfWallets = AcceleratedCheckoutConfiguration.shared.defaultWallets.count
382393
let buttonHeight: CGFloat = 48
383394
let gapHeight: CGFloat = 8
384395
return (CGFloat(numberOfWallets) * buttonHeight) + (CGFloat(numberOfWallets - 1) * gapHeight)
@@ -395,16 +406,15 @@ class RCTAcceleratedCheckoutButtonsView: UIView {
395406
}
396407
}
397408

398-
private func convertToShopifyWallets(_ walletStrings: [String]) -> [Wallet] {
399-
return walletStrings.compactMap { walletString in
400-
switch walletString {
401-
case "shopPay":
402-
return .shopPay
403-
case "applePay":
404-
return .applePay
405-
default:
406-
return nil
409+
private func convertToShopifyWallets(_ walletStrings: [String]) throws -> [Wallet] {
410+
return try walletStrings.compactMap { walletString in
411+
guard let wallet = Wallet(rawValue: walletString), wallet != nil else {
412+
let message = "Unknown wallet option: \(String(describing: walletString))"
413+
print("[ShopifyAcceleratedCheckouts] \(message)")
414+
throw NSError(domain: "ShopifyAcceleratedCheckouts", code: 1, userInfo: ["message": message])
407415
}
416+
417+
return wallet
408418
}
409419
}
410420
}

modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit+EventSerialization.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,10 @@ internal enum ShopifyEventSerialization {
166166
return ["state": "loading"]
167167
case .rendered:
168168
return ["state": "rendered"]
169-
case .error:
170-
return ["state": "error"]
169+
case let .error(reason):
170+
return ["state": "error", "reason": reason]
171171
@unknown default:
172-
return ["state": "unknown"]
172+
return ["state": "error", "reason": "unknown"]
173173
}
174174
}
175175
}

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -295,16 +295,12 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate {
295295
@available(iOS 16.0, *)
296296
private func contactFieldsToRequiredContactFields(_ contactFields: [String]) throws -> [ShopifyAcceleratedCheckouts.RequiredContactFields] {
297297
return try contactFields.compactMap {
298-
switch $0 {
299-
case "email":
300-
return ShopifyAcceleratedCheckouts.RequiredContactFields.email
301-
case "phone":
302-
return ShopifyAcceleratedCheckouts.RequiredContactFields.phone
303-
default:
298+
guard let field = ShopifyAcceleratedCheckouts.RequiredContactFields(rawValue: $0), field != nil else {
304299
let message = "Unknown contactField option: \(String(describing: $0))"
305300
print("[ShopifyCheckoutSheetKit] \(message)")
306301
throw NSError(domain: "ShopifyCheckoutSheetKit", code: 1, userInfo: ["message": message])
307302
}
303+
return field
308304
}
309305
}
310306
}

modules/@shopify/checkout-sheet-kit/src/components/AcceleratedCheckoutButtons.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,11 @@ export enum RenderState {
3535
Loading = 'loading',
3636
Rendered = 'rendered',
3737
Error = 'error',
38-
Unknown = 'unknown',
3938
}
4039

4140
export type RenderStateChangeEvent =
4241
| {state: RenderState.Error; reason?: string}
43-
| {state: RenderState.Loading}
44-
| {state: RenderState.Rendered}
45-
| {state: RenderState.Unknown};
42+
| {state: Omit<RenderState, 'Error'>};
4643

4744
export enum ApplePayLabel {
4845
addMoney = 'addMoney',
@@ -328,10 +325,20 @@ export const AcceleratedCheckoutButtons: React.FC<
328325
export default AcceleratedCheckoutButtons;
329326

330327
function validRenderState(state: string): RenderState {
331-
return (
332-
Object.values(RenderState).find(renderState => renderState === state) ??
333-
RenderState.Unknown
334-
);
328+
switch (state) {
329+
case RenderState.Loading:
330+
return RenderState.Loading;
331+
case RenderState.Rendered:
332+
return RenderState.Rendered;
333+
case RenderState.Error:
334+
return RenderState.Error;
335+
default:
336+
// eslint-disable-next-line no-console
337+
console.error(
338+
`[ShopifyAcceleratedCheckouts] Invalid render state: ${state}`,
339+
);
340+
return RenderState.Error;
341+
}
335342
}
336343

337344
function isCartProps(

modules/@shopify/checkout-sheet-kit/tests/AcceleratedCheckoutButtons.test.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ describe('AcceleratedCheckoutButtons', () => {
250250
nativeEvent: {state: 'unexpected'},
251251
});
252252
expect(onRenderStateChange).toHaveBeenCalledWith({
253-
state: RenderState.Unknown,
253+
state: RenderState.Error,
254254
});
255255
});
256256

@@ -368,5 +368,19 @@ describe('AcceleratedCheckoutButtons', () => {
368368
expect(RenderState.Rendered).toBe('rendered');
369369
expect(RenderState.Error).toBe('error');
370370
});
371+
372+
it('logs unexpected render states', async () => {
373+
const {getByTestId} = render(
374+
<AcceleratedCheckoutButtons cartId="gid://shopify/Cart/123" />,
375+
);
376+
const nativeComponent = getByTestId('accelerated-checkout-buttons');
377+
nativeComponent.props.onRenderStateChange({
378+
nativeEvent: {state: 'unexpected'},
379+
});
380+
381+
expect(mockError).toHaveBeenCalledWith(
382+
'[ShopifyAcceleratedCheckouts] Invalid render state: unexpected',
383+
);
384+
});
371385
});
372386
});

sample/Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ GEM
9191
netrc (0.11.0)
9292
nkf (0.2.0)
9393
public_suffix (4.0.7)
94-
rexml (3.4.1)
94+
rexml (3.4.2)
9595
ruby-macho (2.5.1)
9696
securerandom (0.4.1)
9797
typhoeus (1.4.1)
@@ -117,7 +117,7 @@ DEPENDENCIES
117117
xcodeproj (< 1.26.0)
118118

119119
RUBY VERSION
120-
ruby 3.3.6p108
120+
ruby 3.1.2p20
121121

122122
BUNDLED WITH
123123
2.5.23

sample/ios/Podfile.lock

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1656,8 +1656,8 @@ PODS:
16561656
- Yoga
16571657
- RNShopifyCheckoutSheetKit (3.4.0-rc.1):
16581658
- React-Core
1659-
- ShopifyCheckoutSheetKit (~> 3.4.0-rc.5)
1660-
- ShopifyCheckoutSheetKit/AcceleratedCheckouts (~> 3.4.0-rc.5)
1659+
- ShopifyCheckoutSheetKit (~> 3.4.0-rc.6)
1660+
- ShopifyCheckoutSheetKit/AcceleratedCheckouts (~> 3.4.0-rc.6)
16611661
- RNVectorIcons (10.3.0):
16621662
- DoubleConversion
16631663
- glog
@@ -1679,11 +1679,11 @@ PODS:
16791679
- ReactCommon/turbomodule/bridging
16801680
- ReactCommon/turbomodule/core
16811681
- Yoga
1682-
- ShopifyCheckoutSheetKit (3.4.0-rc.5):
1683-
- ShopifyCheckoutSheetKit/Core (= 3.4.0-rc.5)
1684-
- ShopifyCheckoutSheetKit/AcceleratedCheckouts (3.4.0-rc.5):
1682+
- ShopifyCheckoutSheetKit (3.4.0-rc.6):
1683+
- ShopifyCheckoutSheetKit/Core (= 3.4.0-rc.6)
1684+
- ShopifyCheckoutSheetKit/AcceleratedCheckouts (3.4.0-rc.6):
16851685
- ShopifyCheckoutSheetKit/Core
1686-
- ShopifyCheckoutSheetKit/Core (3.4.0-rc.5)
1686+
- ShopifyCheckoutSheetKit/Core (3.4.0-rc.6)
16871687
- SocketRocket (0.7.0)
16881688
- Yoga (0.0.0)
16891689

@@ -1977,9 +1977,9 @@ SPEC CHECKSUMS:
19771977
RNGestureHandler: 94ed503f49baffca0b1c048d5401c5a393a2660a
19781978
RNReanimated: 9d34c9eb2e2265f66d0ca982f994088da24ca1f7
19791979
RNScreens: 4a5ab20b324ed1e3fe3862796b8f8e0a6208c415
1980-
RNShopifyCheckoutSheetKit: 970346698720e3489cb6331263b076233d94799a
1980+
RNShopifyCheckoutSheetKit: aaa8b936e19e0da7372cfbe8dc4fc1c9eeb2fffa
19811981
RNVectorIcons: 18f6874f831ee0c755e31bb16b0f746c093fc0d8
1982-
ShopifyCheckoutSheetKit: 0a0c5626057297d17ac47c39adb7672f40c5ac26
1982+
ShopifyCheckoutSheetKit: 8806862a40b6a6fa3e55abbb6278cb77b7863e63
19831983
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
19841984
Yoga: 1dd9dabb9df8fe08f12cd522eae04a2da0e252eb
19851985

sample/ios/ReactNative.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
6A86196D2BF36EB900E5EE1A /* CheckoutDidFailTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A86196C2BF36EB900E5EE1A /* CheckoutDidFailTests.swift */; };
1616
6AEEAAB22C00010100E5EE1B /* AcceleratedCheckouts_SupportedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AEEAAB02C00010100E5EE1B /* AcceleratedCheckouts_SupportedTests.swift */; };
1717
6AEEAAB32C00010100E5EE1B /* AcceleratedCheckouts_UnsupportedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AEEAAB12C00010100E5EE1B /* AcceleratedCheckouts_UnsupportedTests.swift */; };
18+
6AF1E0032C00010100E5EE1B /* EventSerializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AF1E0022C00010100E5EE1B /* EventSerializationTests.swift */; };
1819
6AFAD2D22BA9DEF8001F9644 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 6AFAD2D12BA9DEF8001F9644 /* Localizable.xcstrings */; };
1920
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
2021
9403907249B4B6D988902B48 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1939D329B2C035D1A15E316B /* PrivacyInfo.xcprivacy */; };
@@ -54,6 +55,7 @@
5455
6AF2B3762B0BCA6600C6CE4F /* ReactNative-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ReactNative-Bridging-Header.h"; sourceTree = "<group>"; };
5556
6AFAD2D12BA9DEF8001F9644 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = ReactNative/Localizable.xcstrings; sourceTree = "<group>"; };
5657
6AFC2CB62B0D5814003B5A63 /* libShopifyCheckoutSheetKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libShopifyCheckoutSheetKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
58+
6AF1E0022C00010100E5EE1B /* EventSerializationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventSerializationTests.swift; sourceTree = "<group>"; };
5759
7C155D76CCB2E42C0EBB1841 /* libPods-ReactNative-ReactNativeTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNative-ReactNativeTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
5860
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReactNative/LaunchScreen.storyboard; sourceTree = "<group>"; };
5961
9E0AC43398437CA0E3D05DDF /* libPods-ReactNative.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNative.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -88,6 +90,7 @@
8890
6A86196C2BF36EB900E5EE1A /* CheckoutDidFailTests.swift */,
8991
6AEEAAB02C00010100E5EE1B /* AcceleratedCheckouts_SupportedTests.swift */,
9092
6AEEAAB12C00010100E5EE1B /* AcceleratedCheckouts_UnsupportedTests.swift */,
93+
6AF1E0022C00010100E5EE1B /* EventSerializationTests.swift */,
9194
);
9295
path = ReactNativeTests;
9396
sourceTree = "<group>";
@@ -423,6 +426,7 @@
423426
6A86196D2BF36EB900E5EE1A /* CheckoutDidFailTests.swift in Sources */,
424427
6AEEAAB22C00010100E5EE1B /* AcceleratedCheckouts_SupportedTests.swift in Sources */,
425428
6AEEAAB32C00010100E5EE1B /* AcceleratedCheckouts_UnsupportedTests.swift in Sources */,
429+
6AF1E0032C00010100E5EE1B /* EventSerializationTests.swift in Sources */,
426430
);
427431
runOnlyForDeploymentPostprocessing = 0;
428432
};

0 commit comments

Comments
 (0)