diff --git a/.github/actions/install-cocoapods/action.yml b/.github/actions/install-cocoapods/action.yml index 9ec7f7e3..9b48ef8a 100644 --- a/.github/actions/install-cocoapods/action.yml +++ b/.github/actions/install-cocoapods/action.yml @@ -17,7 +17,7 @@ runs: with: path: | sample/ios/Pods - key: ${{ runner.os }}-cocoapods-${{ hashFiles('sample/ios/Podfile.lock', 'sample/Gemfile.lock', 'sample/Gemfile', 'package.json', 'sample/package.json', 'modules/@shopify/checkout-sheet-kit/package.json', 'yarn.lock') }} + key: ${{ runner.os }}-cocoapods-na-${{ env.RCT_NEW_ARCH_ENABLED }}-${{ hashFiles('sample/ios/Podfile.lock', 'sample/Gemfile.lock', 'sample/Gemfile', 'package.json', 'sample/package.json', 'modules/@shopify/checkout-sheet-kit/package.json', 'yarn.lock') }} - name: Install cocoapods shell: bash @@ -27,5 +27,6 @@ runs: cd sample bundle install cd ios - bundle exec pod install --deployment --repo-update + echo "RCT_NEW_ARCH_ENABLED: ${{ env.RCT_NEW_ARCH_ENABLED }}" + bundle exec pod install --repo-update cd $ROOT diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 665a5f2e..f70871f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,10 +83,18 @@ jobs: create-new-comment: false test-android: - name: Run Android Tests + name: Run Android Tests (${{ matrix.arch }}) runs-on: ubuntu-latest timeout-minutes: 20 needs: [lint, test] + strategy: + fail-fast: false + matrix: + include: + - arch: Legacy arch + new_arch_enabled: 'false' + - arch: New arch + new_arch_enabled: 'true' steps: - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -101,11 +109,12 @@ jobs: distribution: 'zulu' java-version: ${{ env.JAVA_VERSION }} - - name: Run Android tests + - name: Run Android tests (${{ matrix.arch }}) timeout-minutes: 20 env: GRADLE_OPTS: -Xmx4g -XX:MaxMetaspaceSize=768m JAVA_HOME: ${{ steps.setup-java.outputs.path }} + ORG_GRADLE_PROJECT_newArchEnabled: ${{ matrix.new_arch_enabled }} run: | echo "JAVA_HOME: $JAVA_HOME" java -version @@ -115,10 +124,18 @@ jobs: yarn sample test:android --no-daemon test-ios: - name: Run Swift Tests + name: Run Swift Tests (${{ matrix.arch }}) runs-on: macos-15-xlarge timeout-minutes: 20 needs: [lint, test] + strategy: + fail-fast: false + matrix: + include: + - arch: Legacy arch + new_arch_enabled: 'false' + - arch: New arch + new_arch_enabled: 'true' steps: - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -139,7 +156,11 @@ jobs: - name: Install cocoapods uses: ./.github/actions/install-cocoapods + env: + RCT_NEW_ARCH_ENABLED: ${{ matrix.new_arch_enabled }} - - name: Run Swift tests + - name: Run Swift tests (${{ matrix.arch }}) + env: + RCT_NEW_ARCH_ENABLED: ${{ matrix.new_arch_enabled }} run: | yarn sample test:ios diff --git a/modules/@shopify/checkout-sheet-kit/android/build.gradle b/modules/@shopify/checkout-sheet-kit/android/build.gradle index 82b9a034..a23704f0 100644 --- a/modules/@shopify/checkout-sheet-kit/android/build.gradle +++ b/modules/@shopify/checkout-sheet-kit/android/build.gradle @@ -80,6 +80,17 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + // Note: this is where the module class is chosen, based on the RN architecture + sourceSets { + main.java.srcDirs += ["src/shared/java"] + + if (isNewArchitectureEnabled()) { + main.java.srcDirs += ["src/newarch/java"] + } else { + main.java.srcDirs += ["src/legacy/java"] + } + } } repositories { diff --git a/modules/@shopify/checkout-sheet-kit/android/src/legacy/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java b/modules/@shopify/checkout-sheet-kit/android/src/legacy/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java new file mode 100644 index 00000000..447d51cb --- /dev/null +++ b/modules/@shopify/checkout-sheet-kit/android/src/legacy/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java @@ -0,0 +1,142 @@ +/* +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. +*/ +package com.shopify.reactnative.checkoutsheetkit; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; + +import java.util.Map; + +/** + * @note + * + * This is the legacy module class for the ShopifyCheckoutSheetKit module. + * It is used to support applications using React Native with the old + * legacy architecture. + */ + +public class ShopifyCheckoutSheetKitModule extends ReactContextBaseJavaModule { + private final CheckoutKitModule common; + public static com.shopify.checkoutsheetkit.Configuration checkoutConfig; + + public ShopifyCheckoutSheetKitModule(ReactApplicationContext reactContext) { + super(reactContext); + this.common = new CheckoutKitModule(reactContext); + ShopifyCheckoutSheetKitModule.checkoutConfig = CheckoutKitModule.checkoutConfig; + } + + @Override + public String getName() { + return common.getName(); + } + + @Override + public Map getConstants() { + return common.getConstants(); + } + + @ReactMethod + public void addListener(String eventName) { + common.addListener(eventName); + } + + @ReactMethod + public void configureAcceleratedCheckouts( + String storefrontDomain, + String storefrontAccessToken, + String customerEmail, + String customerPhoneNumber, + String customerAccessToken, + String applePayMerchantIdentifier, + ReadableArray applePayContactFields, + Promise promise) { + common.configureAcceleratedCheckouts( + storefrontDomain, + storefrontAccessToken, + customerEmail, + customerPhoneNumber, + customerAccessToken, + applePayMerchantIdentifier, + applePayContactFields, + promise); + } + + @ReactMethod + public void dismiss() { + common.dismiss(); + } + + @ReactMethod + public void getConfig(Promise promise) { + common.getConfig(promise); + } + + public String getVersion() { + return common.getVersion(); + } + + @ReactMethod + public void initiateGeolocationRequest(boolean allow) { + common.initiateGeolocationRequest(allow); + } + + @ReactMethod + public void invalidateCache() { + common.invalidateCache(); + } + + @ReactMethod + public void isAcceleratedCheckoutAvailable(Promise promise) { + common.isAcceleratedCheckoutAvailable(promise); + } + + @ReactMethod + public void isApplePayAvailable(Promise promise) { + common.isApplePayAvailable(promise); + } + + @ReactMethod + public void preload(String checkoutURL) { + common.preload(checkoutURL); + } + + @ReactMethod + public void present(String checkoutURL) { + common.present(checkoutURL); + } + + @ReactMethod + public void removeListeners(double count) { + common.removeListeners(count); + } + + @ReactMethod + public void setConfig(ReadableMap config) { + common.setConfig(config); + ShopifyCheckoutSheetKitModule.checkoutConfig = CheckoutKitModule.checkoutConfig; + } +} diff --git a/modules/@shopify/checkout-sheet-kit/android/src/newarch/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java b/modules/@shopify/checkout-sheet-kit/android/src/newarch/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java new file mode 100644 index 00000000..b7a4a817 --- /dev/null +++ b/modules/@shopify/checkout-sheet-kit/android/src/newarch/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java @@ -0,0 +1,137 @@ +/* +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. +*/ +package com.shopify.reactnative.checkoutsheetkit; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; + +import java.util.Map; + +/** + * @note + * + * This is the new architecture module class for the + * ShopifyCheckoutSheetKit module. + * It is used to support applications using React Native with the new + * new architecture. + */ + +public class ShopifyCheckoutSheetKitModule extends NativeShopifyCheckoutSheetKitSpec { + private final CheckoutKitModule common; + public static com.shopify.checkoutsheetkit.Configuration checkoutConfig; + + public ShopifyCheckoutSheetKitModule(ReactApplicationContext reactContext) { + super(reactContext); + this.common = new CheckoutKitModule(reactContext); + ShopifyCheckoutSheetKitModule.checkoutConfig = CheckoutKitModule.checkoutConfig; + } + + @Override + public Map getConstants() { + return common.getConstants(); + } + + @Override + public void addListener(String eventName) { + common.addListener(eventName); + } + + @Override + public void removeListeners(double count) { + common.removeListeners(count); + } + + @Override + public void present(String checkoutURL) { + common.present(checkoutURL); + } + + @Override + public void dismiss() { + common.dismiss(); + } + + @Override + public void preload(String checkoutURL) { + common.preload(checkoutURL); + } + + @Override + public void invalidateCache() { + common.invalidateCache(); + } + + @Override + public void getConfig(Promise promise) { + common.getConfig(promise); + } + + @Override + public void setConfig(ReadableMap config) { + common.setConfig(config); + ShopifyCheckoutSheetKitModule.checkoutConfig = CheckoutKitModule.checkoutConfig; + } + + @Override + public void isApplePayAvailable(Promise promise) { + common.isApplePayAvailable(promise); + } + + @Override + public void isAcceleratedCheckoutAvailable(Promise promise) { + common.isAcceleratedCheckoutAvailable(promise); + } + + @Override + public void configureAcceleratedCheckouts( + String storefrontDomain, + String storefrontAccessToken, + String customerEmail, + String customerPhoneNumber, + String customerAccessToken, + String applePayMerchantIdentifier, + ReadableArray applePayContactFields, + Promise promise) { + common.configureAcceleratedCheckouts( + storefrontDomain, + storefrontAccessToken, + customerEmail, + customerPhoneNumber, + customerAccessToken, + applePayMerchantIdentifier, + applePayContactFields, + promise); + } + + @Override + public void initiateGeolocationRequest(boolean allow) { + common.initiateGeolocationRequest(allow); + } + + @Override + public String getVersion() { + return common.getVersion(); + } +} diff --git a/modules/@shopify/checkout-sheet-kit/android/src/shared/java/com/shopify/reactnative/checkoutsheetkit/CheckoutKitDelegate.java b/modules/@shopify/checkout-sheet-kit/android/src/shared/java/com/shopify/reactnative/checkoutsheetkit/CheckoutKitDelegate.java new file mode 100644 index 00000000..419bca8b --- /dev/null +++ b/modules/@shopify/checkout-sheet-kit/android/src/shared/java/com/shopify/reactnative/checkoutsheetkit/CheckoutKitDelegate.java @@ -0,0 +1,62 @@ +/* +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. +*/ +package com.shopify.reactnative.checkoutsheetkit; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; + +import java.util.Map; + +public interface CheckoutKitDelegate { + void addListener(String eventName); + + void configureAcceleratedCheckouts(String domain, String token, String email, String phone, String customerToken, + String applePayMerchantId, ReadableArray contactFields, Promise promise); + + void dismiss(); + + void getConfig(Promise promise); + + String getName(); + + Map getConstants(); + + String getVersion(); + + void initiateGeolocationRequest(boolean allow); + + void invalidateCache(); + + void isAcceleratedCheckoutAvailable(Promise promise); + + void isApplePayAvailable(Promise promise); + + void preload(String checkoutUrl); + + void present(String checkoutUrl); + + void removeListeners(double count); + + void setConfig(ReadableMap config); +} diff --git a/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java b/modules/@shopify/checkout-sheet-kit/android/src/shared/java/com/shopify/reactnative/checkoutsheetkit/CheckoutKitModule.java similarity index 87% rename from modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java rename to modules/@shopify/checkout-sheet-kit/android/src/shared/java/com/shopify/reactnative/checkoutsheetkit/CheckoutKitModule.java index dd778fcf..20196282 100644 --- a/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java +++ b/modules/@shopify/checkout-sheet-kit/android/src/shared/java/com/shopify/reactnative/checkoutsheetkit/CheckoutKitModule.java @@ -20,17 +20,14 @@ of this software and associated documentation files (the "Software"), to deal 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. */ - package com.shopify.reactnative.checkoutsheetkit; import android.app.Activity; -import android.content.Context; import androidx.activity.ComponentActivity; -import androidx.annotation.NonNull; + import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableNativeMap; import com.shopify.checkoutsheetkit.*; @@ -39,9 +36,7 @@ of this software and associated documentation files (the "Software"), to deal import java.util.Map; import java.util.Objects; -public class ShopifyCheckoutSheetKitModule extends ReactContextBaseJavaModule { - private static final String MODULE_NAME = "ShopifyCheckoutSheetKit"; - +public final class CheckoutKitModule implements CheckoutKitDelegate { public static Configuration checkoutConfig = new Configuration(); private final ReactApplicationContext reactContext; @@ -50,10 +45,8 @@ public class ShopifyCheckoutSheetKitModule extends ReactContextBaseJavaModule { private CustomCheckoutEventProcessor checkoutEventProcessor; - public ShopifyCheckoutSheetKitModule(ReactApplicationContext reactContext) { - super(reactContext); - - this.reactContext = reactContext; + public CheckoutKitModule(ReactApplicationContext ctx) { + this.reactContext = ctx; ShopifyCheckoutSheetKit.configure(configuration -> { configuration.setPlatform(Platform.REACT_NATIVE); @@ -61,10 +54,9 @@ public ShopifyCheckoutSheetKitModule(ReactApplicationContext reactContext) { }); } - @NonNull @Override public String getName() { - return MODULE_NAME; + return "ShopifyCheckoutSheetKit"; } @Override @@ -74,29 +66,34 @@ public Map getConstants() { return constants; } - @ReactMethod + @Override + public String getVersion() { + return ShopifyCheckoutSheetKit.version; + } + + @Override public void addListener(String eventName) { // No-op but required for RN to register module } - @ReactMethod - public void removeListeners(Integer count) { + @Override + public void removeListeners(double count) { // No-op but required for RN to register module } - @ReactMethod - public void present(String checkoutURL) { + @Override + public void present(String checkoutUrl) { Activity currentActivity = getCurrentActivity(); if (currentActivity instanceof ComponentActivity) { checkoutEventProcessor = new CustomCheckoutEventProcessor(currentActivity, this.reactContext); currentActivity.runOnUiThread(() -> { - checkoutSheet = ShopifyCheckoutSheetKit.present(checkoutURL, (ComponentActivity) currentActivity, + checkoutSheet = ShopifyCheckoutSheetKit.present(checkoutUrl, (ComponentActivity) currentActivity, checkoutEventProcessor); }); } } - @ReactMethod + @Override public void dismiss() { if (checkoutSheet != null) { checkoutSheet.dismiss(); @@ -104,21 +101,20 @@ public void dismiss() { } } - @ReactMethod - public void preload(String checkoutURL) { + @Override + public void preload(String checkoutUrl) { Activity currentActivity = getCurrentActivity(); - if (currentActivity instanceof ComponentActivity) { - ShopifyCheckoutSheetKit.preload(checkoutURL, (ComponentActivity) currentActivity); + ShopifyCheckoutSheetKit.preload(checkoutUrl, (ComponentActivity) currentActivity); } } - @ReactMethod + @Override public void invalidateCache() { ShopifyCheckoutSheetKit.invalidate(); } - @ReactMethod + @Override public void getConfig(Promise promise) { WritableNativeMap resultConfig = new WritableNativeMap(); @@ -128,10 +124,8 @@ public void getConfig(Promise promise) { promise.resolve(resultConfig); } - @ReactMethod + @Override public void setConfig(ReadableMap config) { - Context context = getReactApplicationContext(); - ShopifyCheckoutSheetKit.configure(configuration -> { if (config.hasKey("preloading")) { configuration.setPreloading(new Preloading(config.getBoolean("preloading"))); @@ -162,15 +156,35 @@ public void setConfig(ReadableMap config) { }); } - @ReactMethod - public void initiateGeolocationRequest(Boolean allow) { + @Override + public void initiateGeolocationRequest(boolean allow) { if (checkoutEventProcessor != null) { checkoutEventProcessor.invokeGeolocationCallback(allow); } } + @Override + public void isAcceleratedCheckoutAvailable(Promise promise) { + promise.resolve(false); + } + + @Override + public void isApplePayAvailable(Promise promise) { + promise.resolve(false); + } + + @Override + public void configureAcceleratedCheckouts(String domain, String token, String email, String phone, + String customerToken, String applePayMerchantId, ReadableArray contactFields, Promise promise) { + promise.resolve(false); + } + // Private + private Activity getCurrentActivity() { + return reactContext.getCurrentActivity(); + } + private ColorScheme getColorScheme(String colorScheme) { switch (colorScheme) { case "web_default": @@ -257,10 +271,10 @@ private Colors createColorsFromConfig(ReadableMap config) { headerBackground, headerFont, progressIndicator, - // Parameter allows passing a custom drawable, we'll just support custom color for now + // Parameter allows passing a custom drawable, we'll just support custom color + // for now null, - closeButtonColor - ); + closeButtonColor); } return null; diff --git a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm index c70f3248..93991916 100644 --- a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm +++ b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm @@ -25,7 +25,7 @@ of this software and associated documentation files (the "Software"), to deal #import #import -@interface RCT_EXTERN_MODULE (RCTShopifyCheckoutSheetKit, NSObject) +@interface RCT_EXTERN_REMAP_MODULE (ShopifyCheckoutSheetKit, RCTShopifyCheckoutSheetKit, NSObject) /** * Present checkout @@ -75,7 +75,9 @@ @interface RCT_EXTERN_MODULE (RCTShopifyCheckoutSheetKit, NSObject) /** * Check if Apple Pay is available */ -RCT_EXTERN_METHOD(isApplePayAvailable : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); +RCT_EXTERN_METHOD(isApplePayAvailable : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) + +RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(getVersion) @end diff --git a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift index b19012cf..82f9bd5b 100644 --- a/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift +++ b/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift @@ -102,6 +102,10 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate { ] } + @objc func getVersion() -> String { + return ShopifyCheckoutSheetKit.version + } + static func getRootViewController() -> UIViewController? { return (UIApplication.shared.connectedScenes .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene)?.windows diff --git a/modules/@shopify/checkout-sheet-kit/package.json b/modules/@shopify/checkout-sheet-kit/package.json index 8ff762e7..74dfbb8d 100644 --- a/modules/@shopify/checkout-sheet-kit/package.json +++ b/modules/@shopify/checkout-sheet-kit/package.json @@ -63,5 +63,17 @@ } ] ] + }, + "codegenConfig": { + "name": "ShopifyCheckoutSheetKitSpec", + "type": "all", + "jsSrcsDir": "src", + "android": { + "javaPackageName": "com.shopify.reactnative.checkoutsheetkit" + }, + "ios": { + "libraryName": "RNShopifyCheckoutSheetKit", + "prefix": "Shopify" + } } } diff --git a/modules/@shopify/checkout-sheet-kit/package.snapshot.json b/modules/@shopify/checkout-sheet-kit/package.snapshot.json index 782ccbaa..091cac94 100644 --- a/modules/@shopify/checkout-sheet-kit/package.snapshot.json +++ b/modules/@shopify/checkout-sheet-kit/package.snapshot.json @@ -4,11 +4,14 @@ "android/build.gradle", "android/gradle.properties", "android/proguard-rules.pro", + "android/src/legacy/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java", "android/src/main/AndroidManifest.xml", "android/src/main/AndroidManifestNew.xml", "android/src/main/java/com/shopify/reactnative/checkoutsheetkit/CustomCheckoutEventProcessor.java", - "android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java", "android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitPackage.java", + "android/src/newarch/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java", + "android/src/shared/java/com/shopify/reactnative/checkoutsheetkit/CheckoutKitDelegate.java", + "android/src/shared/java/com/shopify/reactnative/checkoutsheetkit/CheckoutKitModule.java", "ios/AcceleratedCheckoutButtons.swift", "ios/AcceleratedCheckoutButtons+Extensions.swift", "ios/ShopifyCheckoutSheetKit-Bridging-Header.h", @@ -28,6 +31,10 @@ "lib/commonjs/index.d.js.map", "lib/commonjs/index.js", "lib/commonjs/index.js.map", + "lib/commonjs/native/module.js", + "lib/commonjs/native/module.js.map", + "lib/commonjs/native/NativeShopifyCheckoutSheetKit.js", + "lib/commonjs/native/NativeShopifyCheckoutSheetKit.js.map", "lib/commonjs/pixels.d.js", "lib/commonjs/pixels.d.js.map", "lib/module/components/AcceleratedCheckoutButtons.js", @@ -42,6 +49,10 @@ "lib/module/index.d.js.map", "lib/module/index.js", "lib/module/index.js.map", + "lib/module/native/module.js", + "lib/module/native/module.js.map", + "lib/module/native/NativeShopifyCheckoutSheetKit.js", + "lib/module/native/NativeShopifyCheckoutSheetKit.js.map", "lib/module/pixels.d.js", "lib/module/pixels.d.js.map", "lib/typescript/src/components/AcceleratedCheckoutButtons.d.ts", @@ -50,6 +61,10 @@ "lib/typescript/src/context.d.ts.map", "lib/typescript/src/index.d.ts", "lib/typescript/src/index.d.ts.map", + "lib/typescript/src/native/module.d.ts", + "lib/typescript/src/native/module.d.ts.map", + "lib/typescript/src/native/NativeShopifyCheckoutSheetKit.d.ts", + "lib/typescript/src/native/NativeShopifyCheckoutSheetKit.d.ts.map", "package.json", "src/components/AcceleratedCheckoutButtons.tsx", "src/context.tsx", @@ -57,5 +72,7 @@ "src/events.d.ts", "src/index.d.ts", "src/index.ts", + "src/native/module.ts", + "src/native/NativeShopifyCheckoutSheetKit.ts", "src/pixels.d.ts" ] diff --git a/modules/@shopify/checkout-sheet-kit/src/components/AcceleratedCheckoutButtons.tsx b/modules/@shopify/checkout-sheet-kit/src/components/AcceleratedCheckoutButtons.tsx index 7958fe85..dab20f1d 100644 --- a/modules/@shopify/checkout-sheet-kit/src/components/AcceleratedCheckoutButtons.tsx +++ b/modules/@shopify/checkout-sheet-kit/src/components/AcceleratedCheckoutButtons.tsx @@ -163,7 +163,7 @@ interface NativeAcceleratedCheckoutButtonsProps { onSizeChange?: (event: {nativeEvent: {height: number}}) => void; } -const RCTAcceleratedCheckoutButtons = +const LegacyAcceleratedCheckoutButtons = requireNativeComponent( 'RCTAcceleratedCheckoutButtons', ); @@ -304,8 +304,24 @@ export const AcceleratedCheckoutButtons: React.FC< } } + /** + * Renderer note: Paper (old renderer) vs Fabric (new renderer). + * + * We intentionally keep supporting both: + * - When Fabric is enabled (RN 0.80+), the Legacy ViewManager Interop layer auto-registers + * an interop component that proxies mount/prop updates/events to our existing Paper + * `RCTViewManager` (`RCTAcceleratedCheckoutButtons`). No dedicated Fabric ComponentView + * is required for this path to work. + * - When Fabric is disabled, this goes through the classic Paper UIManager path. + * + * This preserves the public API while we migrate. When we switch to a true Fabric component + * (codegen + `RCTViewComponentView`), we can replace this with the generated component and + * remove the interop/legacy manager entirely. + */ + const NativeComponent = LegacyAcceleratedCheckoutButtons; + return ( - ; + setConfig(configuration: ConfigurationInput): void; + configureAcceleratedCheckouts( + storefrontDomain: string, + storefrontAccessToken: string, + customerEmail: string | null, + customerPhoneNumber: string | null, + customerAccessToken: string | null, + applePayMerchantIdentifier: string | null, + applePayContactFields: ReadonlyArray<'email' | 'phone'>, + ): Promise; + isAcceleratedCheckoutAvailable(): Promise; + isApplePayAvailable(): Promise; + initiateGeolocationRequest(allow: boolean): void; + addListener(eventName: string): void; + removeListeners(count: number): void; +} + +export default TurboModuleRegistry.getEnforcing( + 'ShopifyCheckoutSheetKit', +); diff --git a/modules/@shopify/checkout-sheet-kit/src/native/module.ts b/modules/@shopify/checkout-sheet-kit/src/native/module.ts new file mode 100644 index 00000000..f7683cdf --- /dev/null +++ b/modules/@shopify/checkout-sheet-kit/src/native/module.ts @@ -0,0 +1,78 @@ +/* +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 {NativeModules} from 'react-native'; +import type {Configuration} from '../index.d'; + +type NativeShopifyCheckoutSheetKit = { + readonly version: string; + dismiss(): void; + invalidateCache(): void; + preload(checkoutUrl: string): void; + present(checkoutUrl: string): void; + getConfig(): Promise; + setConfig(configuration: Configuration): void; + // iOS-specific accelerated checkouts + configureAcceleratedCheckouts?: ( + storefrontDomain: string, + storefrontAccessToken: string, + customerEmail: string | null, + customerPhoneNumber: string | null, + customerAccessToken: string | null, + applePayMerchantIdentifier: string | null, + applePayContactFields: string[], + ) => Promise; + isAcceleratedCheckoutAvailable?: () => Promise; + isApplePayAvailable?: () => Promise; + // Android-specific geolocation + initiateGeolocationRequest?: (allow: boolean) => void; + // RN event emitter contract for TurboModules + addListener: (eventName: string) => void; + removeListeners: (count: number) => void; +}; + +export function getShopifyCheckoutNativeModule(): + | NativeShopifyCheckoutSheetKit + | undefined { + // Prefer TurboModule if available; fall back to classic bridge. + try { + // Dynamically require to avoid throwing when codegen isn't wired yet. + // If the generated spec is present, default export should be the TurboModule proxy. + const turbo: NativeShopifyCheckoutSheetKit = + require('./NativeShopifyCheckoutSheetKit').default; + + if (turbo != null) { + return turbo; + } + } catch { + // no-op; fall back to classic bridge + } + + const legacy = (NativeModules as any).ShopifyCheckoutSheetKit as + | NativeShopifyCheckoutSheetKit + | undefined; + + return legacy; +} + +export type {NativeShopifyCheckoutSheetKit}; diff --git a/package.json b/package.json index f641f7d3..a8136cce 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "react": "19.1.0", "react-native": "0.80.2", "react-native-dotenv": "^3.4.9", - "react-native-gesture-handler": "2.26.0", + "react-native-gesture-handler": "2.28.0", "react-native-gradle-plugin": "^0.71.19", "react-test-renderer": "19.1.0", "ts-jest": "^29.4.1", diff --git a/sample/Gemfile.lock b/sample/Gemfile.lock index a4eab9b9..13b9ab76 100644 --- a/sample/Gemfile.lock +++ b/sample/Gemfile.lock @@ -81,7 +81,7 @@ GEM mutex_m i18n (1.14.7) concurrent-ruby (~> 1.0) - json (2.13.2) + json (2.15.0) logger (1.7.0) minitest (5.25.5) molinillo (0.8.0) @@ -91,7 +91,7 @@ GEM netrc (0.11.0) nkf (0.2.0) public_suffix (4.0.7) - rexml (3.4.2) + rexml (3.4.4) ruby-macho (2.5.1) securerandom (0.4.1) typhoeus (1.5.0) diff --git a/sample/ios/Podfile b/sample/ios/Podfile index d728b048..9c94cb53 100644 --- a/sample/ios/Podfile +++ b/sample/ios/Podfile @@ -25,7 +25,7 @@ target 'ReactNative' do :path => config[:reactNativePath], # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/..", - :new_arch_enabled => true + :new_arch_enabled => (ENV['RCT_NEW_ARCH_ENABLED'] == '1' || ENV['RCT_NEW_ARCH_ENABLED']&.downcase == 'true') ) target 'ReactNativeTests' do diff --git a/sample/ios/Podfile.lock b/sample/ios/Podfile.lock index 9a5d803f..aabc1661 100644 --- a/sample/ios/Podfile.lock +++ b/sample/ios/Podfile.lock @@ -2245,7 +2245,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNGestureHandler (2.26.0): + - RNGestureHandler (2.28.0): - boost - DoubleConversion - fast_float @@ -2274,7 +2274,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNReanimated (3.18.0): + - RNReanimated (3.19.1): - boost - DoubleConversion - fast_float @@ -2301,11 +2301,11 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/reanimated (= 3.18.0) - - RNReanimated/worklets (= 3.18.0) + - RNReanimated/reanimated (= 3.19.1) + - RNReanimated/worklets (= 3.19.1) - SocketRocket - Yoga - - RNReanimated/reanimated (3.18.0): + - RNReanimated/reanimated (3.19.1): - boost - DoubleConversion - fast_float @@ -2332,10 +2332,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/reanimated/apple (= 3.18.0) + - RNReanimated/reanimated/apple (= 3.19.1) - SocketRocket - Yoga - - RNReanimated/reanimated/apple (3.18.0): + - RNReanimated/reanimated/apple (3.19.1): - boost - DoubleConversion - fast_float @@ -2364,7 +2364,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNReanimated/worklets (3.18.0): + - RNReanimated/worklets (3.19.1): - boost - DoubleConversion - fast_float @@ -2391,10 +2391,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/worklets/apple (= 3.18.0) + - RNReanimated/worklets/apple (= 3.19.1) - SocketRocket - Yoga - - RNReanimated/worklets/apple (3.18.0): + - RNReanimated/worklets/apple (3.19.1): - boost - DoubleConversion - fast_float @@ -2879,8 +2879,8 @@ SPEC CHECKSUMS: ReactCodegen: 37cf3321221b0c4f89b0750dbaf466bc99de7a57 ReactCommon: 592ef441605638b95e533653259254b4bd35ff4f RNCMaskedView: 7e0ce15656772a939ff0d269100bca3a182163c8 - RNGestureHandler: eeb622199ef1fb3a076243131095df1c797072f0 - RNReanimated: 288616f9c66ff4b0911f3862ffcf4104482a2adc + RNGestureHandler: db29d3e7edf41a11d133cc3ffd4029762376ed1a + RNReanimated: 7e1500bf2058454a0dfdb9fb7a342ef55c518b84 RNScreens: 26bb60cdb2ef2ca06fd87feefc495072f25982a7 RNShopifyCheckoutSheetKit: 123b2f5948dd4223ffd7d0b318e09c653afd3c0d RNVectorIcons: be4d047a76ad307ffe54732208fb0498fcb8477f diff --git a/sample/package.json b/sample/package.json index 3f5818d7..a1c31b2b 100644 --- a/sample/package.json +++ b/sample/package.json @@ -14,7 +14,8 @@ "start": "react-native start -- --simulator 'iPhone 15 Pro' --reset-cache", "typecheck": "tsc --noEmit", "test:ios": "sh ./scripts/test_ios", - "test:android": "sh ./scripts/test_android" + "test:android": "sh ./scripts/test_android", + "test:android:legacy": "ORG_GRADLE_PROJECT_newArchEnabled=false sh ./scripts/test_android" }, "dependencies": { "@apollo/client": "^3.13.9", @@ -27,8 +28,8 @@ "jotai": "^2.13.1", "react-native-config": "1.5.6", "react-native-dotenv": "^3.4.11", - "react-native-reanimated": "3.18.0", - "react-native-safe-area-context": "^5.2.0", + "react-native-reanimated": "3.19.1", + "react-native-safe-area-context": "^5.6.1", "react-native-screens": "^4.10.0", "react-native-vector-icons": "^10.3.0" }, diff --git a/sample/scripts/build_android b/sample/scripts/build_android index 424ca874..63f13416 100755 --- a/sample/scripts/build_android +++ b/sample/scripts/build_android @@ -4,4 +4,4 @@ set -ex cd android -./gradlew assembleDebug --no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a +./gradlew assembleDebug --no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a -PnewArchEnabled=${ORG_GRADLE_PROJECT_newArchEnabled:-true} diff --git a/sample/scripts/test_android b/sample/scripts/test_android index b6e4223e..56bcfa77 100755 --- a/sample/scripts/test_android +++ b/sample/scripts/test_android @@ -2,6 +2,14 @@ set -e +NEW_ARCH_ENABLED=${ORG_GRADLE_PROJECT_newArchEnabled:-true} + cd android -./gradlew clean generateAndroidManifestFromTemplate test --no-daemon --console=plain -Dorg.gradle.workers.max=1 -PreactNativeArchitectures=arm64-v8a +if [ "$NEW_ARCH_ENABLED" = "false" ]; then + echo "-> Running tests with Legacy Architecture" +else + echo "-> Running tests with New Architecture" +fi + +./gradlew clean generateAndroidManifestFromTemplate test --no-daemon --console=plain -Dorg.gradle.workers.max=1 -PreactNativeArchitectures=arm64-v8a -PnewArchEnabled=$NEW_ARCH_ENABLED diff --git a/scripts/compare_snapshot b/scripts/compare_snapshot index d6ea3579..9aaf647a 100755 --- a/scripts/compare_snapshot +++ b/scripts/compare_snapshot @@ -1,7 +1,10 @@ #!/bin/bash FILENAME="modules/@shopify/checkout-sheet-kit/package.snapshot.json" -TMP_SNAPSHOT_FILENAME="snapshot.json" +TMP_SNAPSHOT_FILENAME="/tmp/snapshot.json" + +# Always remove the temporary snapshot on exit +trap 'rm -f "$TMP_SNAPSHOT_FILENAME"' EXIT # Set up some colors to use later red=$(tput setaf 1) @@ -30,11 +33,9 @@ else If it NOT intentional, please manually check the diff below to discern if the file(s) should be included in the public package or not: -${green}EXISTING SNAPSHOT${reset} ${red}NEW SNAPSHOT${reset} +${green}EXISTING SNAPSHOT${reset} ${red}NEW SNAPSHOT${reset} " diff --color --side-by-side $FILENAME $TMP_SNAPSHOT_FILENAME exit 1 fi - -rm $TMP_SNAPSHOT_FILENAME diff --git a/yarn.lock b/yarn.lock index 30e5f76c..7f16b92c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6124,7 +6124,7 @@ __metadata: react: "npm:19.1.0" react-native: "npm:0.80.2" react-native-dotenv: "npm:^3.4.9" - react-native-gesture-handler: "npm:2.26.0" + react-native-gesture-handler: "npm:2.28.0" react-native-gradle-plugin: "npm:^0.71.19" react-test-renderer: "npm:19.1.0" ts-jest: "npm:^29.4.1" @@ -11120,9 +11120,9 @@ __metadata: languageName: node linkType: hard -"react-native-gesture-handler@npm:2.26.0": - version: 2.26.0 - resolution: "react-native-gesture-handler@npm:2.26.0" +"react-native-gesture-handler@npm:2.28.0": + version: 2.28.0 + resolution: "react-native-gesture-handler@npm:2.28.0" dependencies: "@egjs/hammerjs": "npm:^2.0.17" hoist-non-react-statics: "npm:^3.3.0" @@ -11130,7 +11130,7 @@ __metadata: peerDependencies: react: "*" react-native: "*" - checksum: 10c0/43aee4f0aeb7be9e2e72a9abaeaff38104c188a44ffc1a5634940a851adca5f10ee3a8d1b4828eb654574d4d45aa0f0c2af5a7b4ed2e9bca4e76d0d932f13955 + checksum: 10c0/4240c8eedca69eb36b5d3e375b71867251cf8b87a755ba7066b3f73cfdbc80574042dbd4ff821041fd1539c4cd90dbf7ee34586f5a0ea6cc38052375b3169f2e languageName: node linkType: hard @@ -11161,9 +11161,9 @@ __metadata: languageName: node linkType: hard -"react-native-reanimated@npm:3.18.0": - version: 3.18.0 - resolution: "react-native-reanimated@npm:3.18.0" +"react-native-reanimated@npm:3.19.1": + version: 3.19.1 + resolution: "react-native-reanimated@npm:3.19.1" dependencies: "@babel/plugin-transform-arrow-functions": "npm:^7.0.0-0" "@babel/plugin-transform-class-properties": "npm:^7.0.0-0" @@ -11181,11 +11181,11 @@ __metadata: "@babel/core": ^7.0.0-0 react: "*" react-native: "*" - checksum: 10c0/7420f410a76b2a46f1a0a7df41f8b2508aade8de26fd00eb7305bf25b3ab27e7c06f52ec76d221d8c7317ca3e2bdf842c51216f2adba7fbb60ce321c171d1437 + checksum: 10c0/82f83f8cd307dbb3d0d435a7c801fa27d06265b1eed51844013682e133072e447fd15c3f12814f4bd8d4759c89ad4b1043631611284afbd8e6eb505f57af0f12 languageName: node linkType: hard -"react-native-safe-area-context@npm:^5.2.0": +"react-native-safe-area-context@npm:^5.6.1": version: 5.6.1 resolution: "react-native-safe-area-context@npm:5.6.1" peerDependencies: @@ -11728,8 +11728,8 @@ __metadata: jotai: "npm:^2.13.1" react-native-config: "npm:1.5.6" react-native-dotenv: "npm:^3.4.11" - react-native-reanimated: "npm:3.18.0" - react-native-safe-area-context: "npm:^5.2.0" + react-native-reanimated: "npm:3.19.1" + react-native-safe-area-context: "npm:^5.6.1" react-native-screens: "npm:^4.10.0" react-native-vector-icons: "npm:^10.3.0" setimmediate: "npm:^1.0.5"