diff --git a/README.md b/README.md index d54f47cd..524c9e03 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,48 @@ minimum version number is at least `13`. + platform :ios, 13 ``` +### 4. Optional: Accelerated Checkout Buttons (iOS only) + +By default, the iOS native module includes only the core checkout functionality. If you want to use the `AcceleratedCheckoutButtons` component (for Shop Pay and Apple Pay buttons), you need to explicitly include it. + +#### Bare React Native or Expo Bare Workflow + +Add the `AcceleratedCheckouts` subspec to your `ios/Podfile`: + +```ruby +# ios/Podfile +pod "RNShopifyCheckoutSheetKit/AcceleratedCheckouts", :path => "../node_modules/@shopify/checkout-sheet-kit" +``` + +#### Expo Managed Workflow + +For Expo managed apps, use the provided config plugin to enable AcceleratedCheckouts. In your `app.json` or `app.config.js`: + +```json +{ + "expo": { + "plugins": [ + [ + "@shopify/checkout-sheet-kit", + { + "enableAcceleratedCheckouts": true + } + ] + ] + } +} +``` + +The config plugin will automatically update your iOS Podfile to include the `AcceleratedCheckouts` subspec. After adding the plugin, run: + +```sh +npx expo prebuild --clean +``` + +To disable AcceleratedCheckouts, set `enableAcceleratedCheckouts` to `false` or omit it entirely. + +If you only need the default checkout experience, no additional configuration is required. + ## Basic Usage Once the SDK has been added as a package dependency and the minimum platform diff --git a/modules/@shopify/checkout-sheet-kit/EXPO_PLUGIN.md b/modules/@shopify/checkout-sheet-kit/EXPO_PLUGIN.md new file mode 100644 index 00000000..aecb06f0 --- /dev/null +++ b/modules/@shopify/checkout-sheet-kit/EXPO_PLUGIN.md @@ -0,0 +1,118 @@ +# Expo Config Plugin + +This package includes an Expo config plugin that allows you to configure the AcceleratedCheckouts feature for iOS in managed Expo apps. + +## Usage + +Add the plugin to your `app.json` or `app.config.js`: + +```json +{ + "expo": { + "plugins": [ + [ + "@shopify/checkout-sheet-kit", + { + "enableAcceleratedCheckouts": true + } + ] + ] + } +} +``` + +After adding the plugin, regenerate your native projects: + +```sh +npx expo prebuild --clean +``` + +## Configuration Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `enableAcceleratedCheckouts` | `boolean` | `false` | Whether to include the AcceleratedCheckouts subspec for Shop Pay and Apple Pay buttons | + +## How It Works + +The config plugin uses Expo's `withDangerousMod` to modify your iOS `Podfile` during the prebuild process: + +1. **When `enableAcceleratedCheckouts: true`**: + - Searches for the `RNShopifyCheckoutSheetKit` pod declaration in your Podfile + - Updates it to use the `AcceleratedCheckouts` subspec + - Example: `pod 'RNShopifyCheckoutSheetKit/AcceleratedCheckouts', :path => '...'` + +2. **When `enableAcceleratedCheckouts: false` (or omitted)**: + - Ensures the Podfile uses the default Core subspec only + - Example: `pod 'RNShopifyCheckoutSheetKit', :path => '...'` + +## Technical Details + +### Plugin Implementation + +The plugin (`app.plugin.js`) performs the following operations: + +1. Locates the `Podfile` in your iOS project root +2. Uses regex patterns to detect existing pod configurations: + - Core-only: `pod 'RNShopifyCheckoutSheetKit'` + - With AcceleratedCheckouts: `pod 'RNShopifyCheckoutSheetKit/AcceleratedCheckouts'` +3. Modifies the Podfile content based on the `enableAcceleratedCheckouts` setting +4. Writes the updated content back to the Podfile + +### Why Use a Config Plugin? + +Expo's autolinking mechanism doesn't natively support CocoaPods subspecs. Without this plugin, managed Expo apps would always get the default (Core-only) subspec. The config plugin bridges this gap by programmatically modifying the Podfile during the prebuild process. + +### Bare Workflow Alternative + +If you're using Expo's bare workflow or a standard React Native app, you can skip the config plugin and directly modify your `ios/Podfile`: + +```ruby +# ios/Podfile +pod "RNShopifyCheckoutSheetKit/AcceleratedCheckouts", :path => "../node_modules/@shopify/checkout-sheet-kit" +``` + +## Troubleshooting + +### Plugin not taking effect + +Make sure to run `npx expo prebuild --clean` after modifying your plugin configuration. The `--clean` flag ensures the iOS project is regenerated from scratch. + +### CocoaPods errors after prebuild + +If you encounter CocoaPods errors, try: + +```sh +cd ios +pod install --repo-update +cd .. +``` + +### Verifying the configuration + +After running prebuild, check your `ios/Podfile` to verify the correct subspec is being used: + +```sh +grep "RNShopifyCheckoutSheetKit" ios/Podfile +``` + +Expected output with AcceleratedCheckouts enabled: +``` +pod 'RNShopifyCheckoutSheetKit/AcceleratedCheckouts', :path => '../node_modules/@shopify/checkout-sheet-kit' +``` + +## Development + +To test the plugin locally during development: + +1. Make changes to `app.plugin.js` +2. Run `npx expo prebuild --clean` in your test app +3. Verify the Podfile changes are correct +4. Run `pod install` in the iOS directory +5. Build and test the app + +## Related Documentation + +- [Expo Config Plugins](https://docs.expo.dev/config-plugins/introduction/) +- [CocoaPods Subspecs](https://guides.cocoapods.org/syntax/podspec.html#subspec) +- [Shopify Checkout Kit Documentation](https://github.com/Shopify/checkout-sheet-kit-react-native) diff --git a/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec b/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec index 185b3f48..8666f50a 100644 --- a/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec +++ b/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec @@ -16,12 +16,24 @@ Pod::Spec.new do |s| s.platforms = { :ios => "13.0" } s.source = { :git => "https://github.com/Shopify/checkout-sheet-kit-react-native.git", :tag => "#{s.version}" } + s.swift_version = '5.0' - s.source_files = "ios/*.{h,m,mm,swift}" + s.default_subspec = 'Core' - s.dependency "React-Core" - s.dependency "ShopifyCheckoutSheetKit", "~> 3.4.0-rc.9" - s.dependency "ShopifyCheckoutSheetKit/AcceleratedCheckouts", "~> 3.4.0-rc.9" + s.subspec 'Core' do |ss| + ss.source_files = "ios/ShopifyCheckoutSheetKit*.{h,m,mm,swift}" + ss.dependency "React-Core" + ss.dependency "ShopifyCheckoutSheetKit", "~> 3.4.0-rc.9" + end + + s.subspec 'AcceleratedCheckouts' do |ss| + ss.source_files = "ios/AcceleratedCheckout*.swift" + ss.dependency "RNShopifyCheckoutSheetKit/Core" + ss.dependency "ShopifyCheckoutSheetKit/AcceleratedCheckouts", "~> 3.4.0-rc.9" + ss.pod_target_xcconfig = { + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) RN_SHOPIFY_CHECKOUT_ACCELERATED_CHECKOUTS=1' + } + end if fabric_enabled # Use React Native's helper if available, otherwise add dependencies directly diff --git a/modules/@shopify/checkout-sheet-kit/app.plugin.js b/modules/@shopify/checkout-sheet-kit/app.plugin.js new file mode 100644 index 00000000..5b87b7d1 --- /dev/null +++ b/modules/@shopify/checkout-sheet-kit/app.plugin.js @@ -0,0 +1,95 @@ +const { + withDangerousMod, + withPlugins, + createRunOncePlugin, +} = require('@expo/config-plugins'); +const path = require('path'); +const fs = require('fs'); + +const pkg = require('./package.json'); + +const withAcceleratedCheckouts = (config, props = {}) => { + const enableAcceleratedCheckouts = props.enableAcceleratedCheckouts ?? false; + + return withDangerousMod(config, [ + 'ios', + async config => { + const podfilePath = path.join( + config.modRequest.platformProjectRoot, + 'Podfile', + ); + + if (!fs.existsSync(podfilePath)) { + throw new Error( + `[@shopify/checkout-sheet-kit] Could not find Podfile at ${podfilePath}`, + ); + } + + let podfileContent = fs.readFileSync(podfilePath, 'utf8'); + + const coreSubspecPattern = + /pod\s+['"]RNShopifyCheckoutSheetKit['"]\s*,\s*:path\s*=>\s*['"][^'"]*['"]/; + const acceleratedSubspecPattern = + /pod\s+['"]RNShopifyCheckoutSheetKit\/AcceleratedCheckouts['"]\s*,\s*:path\s*=>\s*['"][^'"]*['"]/; + + const hasCoreSubspec = coreSubspecPattern.test(podfileContent); + const hasAcceleratedSubspec = + acceleratedSubspecPattern.test(podfileContent); + const hasAutolinkedPod = + !hasCoreSubspec && + !hasAcceleratedSubspec && + /pod\s+['"]RNShopifyCheckoutSheetKit['"]/.test(podfileContent); + + if (enableAcceleratedCheckouts) { + if (hasCoreSubspec) { + podfileContent = podfileContent.replace( + coreSubspecPattern, + match => { + return match.replace( + 'RNShopifyCheckoutSheetKit', + 'RNShopifyCheckoutSheetKit/AcceleratedCheckouts', + ); + }, + ); + fs.writeFileSync(podfilePath, podfileContent, 'utf8'); + console.log( + '[@shopify/checkout-sheet-kit] Updated Podfile to use AcceleratedCheckouts subspec', + ); + } else if (!hasAcceleratedSubspec && hasAutolinkedPod) { + podfileContent = podfileContent.replace( + /pod\s+['"]RNShopifyCheckoutSheetKit['"]\s*,\s*:path\s*=>\s*['"]([^'"]*)['"]/, + `pod 'RNShopifyCheckoutSheetKit/AcceleratedCheckouts', :path => '$1'`, + ); + fs.writeFileSync(podfilePath, podfileContent, 'utf8'); + console.log( + '[@shopify/checkout-sheet-kit] Updated Podfile to use AcceleratedCheckouts subspec', + ); + } + } else { + if (hasAcceleratedSubspec) { + podfileContent = podfileContent.replace( + acceleratedSubspecPattern, + match => { + return match.replace( + 'RNShopifyCheckoutSheetKit/AcceleratedCheckouts', + 'RNShopifyCheckoutSheetKit', + ); + }, + ); + fs.writeFileSync(podfilePath, podfileContent, 'utf8'); + console.log( + '[@shopify/checkout-sheet-kit] Updated Podfile to use Core subspec only', + ); + } + } + + return config; + }, + ]); +}; + +module.exports = createRunOncePlugin( + withAcceleratedCheckouts, + pkg.name, + pkg.version, +); diff --git a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm index 2971cd2c..cd85d1e2 100644 --- a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm +++ b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm @@ -79,6 +79,7 @@ @interface RCT_EXTERN_MODULE (RCTShopifyCheckoutSheetKit, NSObject) @end +#if RN_SHOPIFY_CHECKOUT_ACCELERATED_CHECKOUTS /** * AcceleratedCheckoutButtons View Manager */ @@ -147,3 +148,4 @@ @interface RCT_EXTERN_MODULE (RCTAcceleratedCheckoutButtonsManager, RCTViewManag RCT_EXPORT_VIEW_PROPERTY(onSizeChange, RCTDirectEventBlock) @end +#endif diff --git a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift index f7000c03..0832df33 100644 --- a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift +++ b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift @@ -33,8 +33,10 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate { private var hasListeners = false internal var checkoutSheet: UIViewController? + #if RN_SHOPIFY_CHECKOUT_ACCELERATED_CHECKOUTS private var acceleratedCheckoutsConfiguration: Any? private var acceleratedCheckoutsApplePayConfiguration: Any? + #endif override var methodQueue: DispatchQueue! { return DispatchQueue.main @@ -213,6 +215,7 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate { resolve(config) } + #if RN_SHOPIFY_CHECKOUT_ACCELERATED_CHECKOUTS @objc func configureAcceleratedCheckouts( _ storefrontDomain: String, storefrontAccessToken: String, @@ -305,4 +308,34 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate { return field } } + #else + @objc func configureAcceleratedCheckouts( + _ storefrontDomain: String, + storefrontAccessToken: String, + customerEmail: String?, + customerPhoneNumber: String?, + customerAccessToken: String?, + applePayMerchantIdentifier: String?, + applyPayContactFields: [String]?, + supportedShippingCountries: [String]?, + resolve: @escaping RCTPromiseResolveBlock, + reject _: @escaping RCTPromiseRejectBlock + ) { + resolve(false) + } + + @objc func isAcceleratedCheckoutAvailable( + _ resolve: @escaping RCTPromiseResolveBlock, + reject _: @escaping RCTPromiseRejectBlock + ) { + resolve(false) + } + + @objc func isApplePayAvailable( + _ resolve: @escaping RCTPromiseResolveBlock, + reject _: @escaping RCTPromiseRejectBlock + ) { + resolve(false) + } + #endif } diff --git a/modules/@shopify/checkout-sheet-kit/package.json b/modules/@shopify/checkout-sheet-kit/package.json index f036e374..c1efeddf 100644 --- a/modules/@shopify/checkout-sheet-kit/package.json +++ b/modules/@shopify/checkout-sheet-kit/package.json @@ -26,6 +26,7 @@ "ios", "android", "lib", + "app.plugin.js", "*.podspec", "!ios/build", "!android/build", diff --git a/sample/ios/Podfile b/sample/ios/Podfile index 42fcf67c..fb27b920 100644 --- a/sample/ios/Podfile +++ b/sample/ios/Podfile @@ -56,4 +56,4 @@ target 'ReactNative' do end end -pod "RNShopifyCheckoutSheetKit", :path => "../../modules/@shopify/checkout-sheet-kit" +pod "RNShopifyCheckoutSheetKit/AcceleratedCheckouts", :path => "../../modules/@shopify/checkout-sheet-kit" diff --git a/sample/ios/Podfile.lock b/sample/ios/Podfile.lock index 858a0050..97a51b91 100644 --- a/sample/ios/Podfile.lock +++ b/sample/ios/Podfile.lock @@ -2511,10 +2511,70 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - ShopifyCheckoutSheetKit (~> 3.4.0-rc.9) + - RNShopifyCheckoutSheetKit/Core (= 3.4.0-rc.3) + - SocketRocket + - Yoga + - RNShopifyCheckoutSheetKit/AcceleratedCheckouts (3.4.0-rc.3): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNShopifyCheckoutSheetKit/Core - ShopifyCheckoutSheetKit/AcceleratedCheckouts (~> 3.4.0-rc.9) - SocketRocket - Yoga + - RNShopifyCheckoutSheetKit/Core (3.4.0-rc.3): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ShopifyCheckoutSheetKit (~> 3.4.0-rc.9) + - SocketRocket + - Yoga - RNVectorIcons (10.3.0): - boost - DoubleConversion @@ -2632,6 +2692,7 @@ DEPENDENCIES: - RNReanimated (from `../node_modules/react-native-reanimated`) - RNScreens (from `../node_modules/react-native-screens`) - "RNShopifyCheckoutSheetKit (from `../../modules/@shopify/checkout-sheet-kit`)" + - "RNShopifyCheckoutSheetKit/AcceleratedCheckouts (from `../../modules/@shopify/checkout-sheet-kit`)" - RNVectorIcons (from `../node_modules/react-native-vector-icons`) - SocketRocket (~> 0.7.1) - Yoga (from `../../node_modules/react-native/ReactCommon/yoga`) @@ -2882,12 +2943,12 @@ SPEC CHECKSUMS: RNGestureHandler: eeb622199ef1fb3a076243131095df1c797072f0 RNReanimated: 288616f9c66ff4b0911f3862ffcf4104482a2adc RNScreens: 26bb60cdb2ef2ca06fd87feefc495072f25982a7 - RNShopifyCheckoutSheetKit: db2a928b4c0c407b8b837c3262dfa918a20f32e5 + RNShopifyCheckoutSheetKit: efc303e7e3c9934edcd3ddad4092eb61f4e896b0 RNVectorIcons: be4d047a76ad307ffe54732208fb0498fcb8477f ShopifyCheckoutSheetKit: 85c0d745661ae9129ee7cce0db9f4b55c5779adb SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: a742cc68e8366fcfc681808162492bc0aa7a9498 -PODFILE CHECKSUM: 42a4ad42b0177ca0f927363d8f2b6059c6dd77c7 +PODFILE CHECKSUM: 703a21429b87b1b65f3e1717faab5512f633af46 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2