diff --git a/README.md b/README.md index afcd2440..2f577398 100644 --- a/README.md +++ b/README.md @@ -65,20 +65,20 @@ experiences. ## Platform Requirements -- **React Native** - Minimum version `0.76` (v4+) / `0.70` (v3 and earlier) +- **React Native** - Minimum version `0.76` (`3.9.0+`) / `0.70` (`<=3.8.x`) - **iOS** - Minimum version iOS 13 - **Android** - Minimum Java 11 & Android SDK version `23` ## Version Compatibility -Starting with **v4.0.0**, `@shopify/checkout-sheet-kit` requires the React Native +Starting with **v3.9.0**, `@shopify/checkout-sheet-kit` requires the React Native **New Architecture** (TurboModules + Fabric). Apps on the old architecture must -stay on the `v3.x` line until they migrate. +stay on `v3.8.x` until they migrate. | Package version | React Native | Architecture | | --------------- | -------------- | ------------------ | -| `4.x` | `>= 0.76` | New Architecture | -| `3.x` | `>= 0.70` | Old Architecture | +| `3.9.x` | `>= 0.76` | New Architecture | +| `<=3.8.x` | `>= 0.70` | Old Architecture | See the [React Native upgrade guide](https://reactnative.dev/docs/the-new-architecture/use-the-new-architecture) for help enabling the New Architecture in your app. diff --git a/modules/@shopify/checkout-sheet-kit/package.json b/modules/@shopify/checkout-sheet-kit/package.json index c1347ba8..3280f64f 100644 --- a/modules/@shopify/checkout-sheet-kit/package.json +++ b/modules/@shopify/checkout-sheet-kit/package.json @@ -1,7 +1,7 @@ { "name": "@shopify/checkout-sheet-kit", "license": "MIT", - "version": "4.0.0", + "version": "3.9.0", "main": "lib/commonjs/index.js", "types": "src/index.ts", "source": "src/index.ts", diff --git a/modules/@shopify/checkout-sheet-kit/src/context.tsx b/modules/@shopify/checkout-sheet-kit/src/context.tsx index c3a5db4d..713c60c1 100644 --- a/modules/@shopify/checkout-sheet-kit/src/context.tsx +++ b/modules/@shopify/checkout-sheet-kit/src/context.tsx @@ -38,8 +38,8 @@ type Maybe = T | undefined; interface Context { acceleratedCheckoutsAvailable: boolean; addEventListener: AddEventListener; - getConfig: () => Configuration | undefined; - setConfig: (config: Configuration) => void; + getConfig: () => Promise; + setConfig: (config: Configuration) => Promise; removeEventListeners: RemoveEventListeners; preload: (checkoutUrl: string) => void; present: (checkoutUrl: string) => void; @@ -71,24 +71,28 @@ export function ShopifyCheckoutSheetProvider({ } useEffect(() => { - if (!instance.current || !configuration) { - return; - } - - const customer = configuration.acceleratedCheckouts?.customer; - if (customer?.accessToken && (customer?.email || customer?.phoneNumber)) { - // eslint-disable-next-line no-console - console.warn( - '[ShopifyCheckoutSheetKit] Providing accessToken with contactFields (email / phoneNumber) is deprecated and will become an error in v4.' + - 'When the user is authenticated with Customer Accounts, provide accessToken' + - 'When the user is otherwise authenticated, provide email/phoneNumber.', + async function configureCheckoutKit() { + if (!instance.current || !configuration) { + return; + } + + const customer = configuration.acceleratedCheckouts?.customer; + if (customer?.accessToken && (customer?.email || customer?.phoneNumber)) { + // eslint-disable-next-line no-console + console.warn( + '[ShopifyCheckoutSheetKit] Providing accessToken with contactFields (email / phoneNumber) is deprecated and will become an error in v4.' + + 'When the user is authenticated with Customer Accounts, provide accessToken' + + 'When the user is otherwise authenticated, provide email/phoneNumber.', + ); + } + + await instance.current.setConfig(configuration); + setAcceleratedCheckoutsAvailable( + instance.current.acceleratedCheckoutsReady, ); } - instance.current.setConfig(configuration); - setAcceleratedCheckoutsAvailable( - instance.current.acceleratedCheckoutsReady, - ); + configureCheckoutKit(); }, [configuration]); const addEventListener: AddEventListener = useCallback( @@ -122,11 +126,11 @@ export function ShopifyCheckoutSheetProvider({ instance.current?.dismiss(); }, []); - const setConfig = useCallback((config: Configuration) => { - instance.current?.setConfig(config); + const setConfig = useCallback(async (config: Configuration) => { + await instance.current?.setConfig(config); }, []); - const getConfig = useCallback(() => { + const getConfig = useCallback(async () => { return instance.current?.getConfig(); }, []); diff --git a/modules/@shopify/checkout-sheet-kit/src/index.d.ts b/modules/@shopify/checkout-sheet-kit/src/index.d.ts index 5a5d73b7..34147dad 100644 --- a/modules/@shopify/checkout-sheet-kit/src/index.d.ts +++ b/modules/@shopify/checkout-sheet-kit/src/index.d.ts @@ -325,7 +325,7 @@ export interface ShopifyCheckoutSheetKit { /** * Return the current config for the checkout. See README.md for more details. */ - getConfig(): Configuration; + getConfig(): Promise; /** * Listen for checkout events */ @@ -344,10 +344,10 @@ export interface ShopifyCheckoutSheetKit { */ configureAcceleratedCheckouts( config: AcceleratedCheckoutConfiguration, - ): boolean; + ): Promise; /** * Check if accelerated checkout is available for the given cart or product */ - isAcceleratedCheckoutAvailable(): boolean; + isAcceleratedCheckoutAvailable(): Promise; } diff --git a/modules/@shopify/checkout-sheet-kit/src/index.ts b/modules/@shopify/checkout-sheet-kit/src/index.ts index 8becbe0c..bdf97876 100644 --- a/modules/@shopify/checkout-sheet-kit/src/index.ts +++ b/modules/@shopify/checkout-sheet-kit/src/index.ts @@ -151,9 +151,9 @@ class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { /** * Retrieves the current checkout configuration - * @returns The current Configuration + * @returns Promise containing the current Configuration */ - public getConfig(): Configuration { + public async getConfig(): Promise { return this.coerceConfigurationResult(RNShopifyCheckoutSheetKit.getConfig()); } @@ -161,11 +161,12 @@ class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { * Updates the checkout configuration * @param configuration New configuration settings to apply */ - public setConfig(configuration: Configuration): void { + public async setConfig(configuration: Configuration): Promise { if (configuration.acceleratedCheckouts) { - this._acceleratedCheckoutsReady = this.configureAcceleratedCheckouts( - configuration.acceleratedCheckouts, - ); + this._acceleratedCheckoutsReady = + await this.configureAcceleratedCheckouts( + configuration.acceleratedCheckouts, + ); } RNShopifyCheckoutSheetKit.setConfig(configuration); } @@ -233,9 +234,9 @@ class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { * Configure AcceleratedCheckouts for Shop Pay and Apple Pay buttons * @param config Configuration for AcceleratedCheckouts */ - public configureAcceleratedCheckouts( + public async configureAcceleratedCheckouts( config: AcceleratedCheckoutConfiguration, - ): boolean { + ): Promise { if (!this.acceleratedCheckoutsSupported) { return false; } @@ -265,9 +266,9 @@ class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { /** * Check if accelerated checkout is available for the given cart or product - * @returns boolean indicating availability + * @returns Promise indicating availability */ - public isAcceleratedCheckoutAvailable(): boolean { + public async isAcceleratedCheckoutAvailable(): Promise { if (!this.acceleratedCheckoutsSupported) { return false; } diff --git a/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx b/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx index 8f22f503..9e18f686 100644 --- a/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx +++ b/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx @@ -348,7 +348,7 @@ describe('useShopifyCheckoutSheet', () => { , ); - const config = hookValue.getConfig(); + const config = await hookValue.getConfig(); expect(config).toEqual({ preloading: true, colorScheme: 'automatic', diff --git a/modules/@shopify/checkout-sheet-kit/tests/index.test.ts b/modules/@shopify/checkout-sheet-kit/tests/index.test.ts index 8beeb28a..dc87f70d 100644 --- a/modules/@shopify/checkout-sheet-kit/tests/index.test.ts +++ b/modules/@shopify/checkout-sheet-kit/tests/index.test.ts @@ -160,9 +160,9 @@ describe('ShopifyCheckoutSheetKit', () => { }); describe('getConfig', () => { - it('returns the parsed config from the Native Module', () => { + it('returns the parsed config from the Native Module', async () => { const instance = new ShopifyCheckoutSheet(); - expect(instance.getConfig()).toStrictEqual({ + await expect(instance.getConfig()).resolves.toStrictEqual({ preloading: true, colorScheme: ColorScheme.automatic, logLevel: LogLevel.error, @@ -732,7 +732,7 @@ describe('ShopifyCheckoutSheetKit', () => { NativeModule.configureAcceleratedCheckouts.mockReturnValue(true); const result = - instance.configureAcceleratedCheckouts(acceleratedConfig); + await instance.configureAcceleratedCheckouts(acceleratedConfig); expect(result).toBe(true); expect( @@ -757,7 +757,7 @@ describe('ShopifyCheckoutSheetKit', () => { }; NativeModule.configureAcceleratedCheckouts.mockReturnValue(true); - instance.configureAcceleratedCheckouts(minimalConfig); + await instance.configureAcceleratedCheckouts(minimalConfig); expect( NativeModule.configureAcceleratedCheckouts, @@ -778,7 +778,7 @@ describe('ShopifyCheckoutSheetKit', () => { const instance = new ShopifyCheckoutSheet(); const result = - instance.configureAcceleratedCheckouts(acceleratedConfig); + await instance.configureAcceleratedCheckouts(acceleratedConfig); expect(result).toBe(false); expect( @@ -794,9 +794,9 @@ describe('ShopifyCheckoutSheetKit', () => { }; const expectedError = new Error('`storefrontDomain` is required'); - expect( + await expect( instance.configureAcceleratedCheckouts(invalidConfig), - ).toBe(false); + ).resolves.toBe(false); expect(console.error).toHaveBeenCalledWith( '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', expectedError, @@ -812,9 +812,9 @@ describe('ShopifyCheckoutSheetKit', () => { const expectedError = new Error('`storefrontAccessToken` is required'); - expect( + await expect( instance.configureAcceleratedCheckouts(invalidConfig), - ).toBe(false); + ).resolves.toBe(false); expect(console.error).toHaveBeenCalledWith( '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', expectedError, @@ -837,9 +837,9 @@ describe('ShopifyCheckoutSheetKit', () => { '`wallets.applePay.merchantIdentifier` is required', ); - expect( + await expect( instance.configureAcceleratedCheckouts(invalidConfig), - ).toBe(false); + ).resolves.toBe(false); expect(console.error).toHaveBeenCalledWith( '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', expectedError, @@ -862,16 +862,16 @@ describe('ShopifyCheckoutSheetKit', () => { `'wallets.applePay.contactFields' contains unexpected values. Expected "email, phone", received "invalid"`, ); - expect( + await expect( instance.configureAcceleratedCheckouts(invalidConfig as any), - ).toBe(false); + ).resolves.toBe(false); expect(console.error).toHaveBeenCalledWith( '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', expectedError, ); }); - it('does not throw when Apple Pay wallet is not configured', () => { + it('does not throw when Apple Pay wallet is not configured', async () => { const instance = new ShopifyCheckoutSheet(); const configWithoutApplePay = { storefrontDomain: 'test-shop.myshopify.com', @@ -879,12 +879,12 @@ describe('ShopifyCheckoutSheetKit', () => { }; NativeModule.configureAcceleratedCheckouts.mockReturnValue(true); - expect( + await expect( instance.configureAcceleratedCheckouts(configWithoutApplePay), - ).toBe(true); + ).resolves.toBe(true); }); - it('throws when a non-string value is given for supportedShippingCountries', () => { + it('throws when a non-string value is given for supportedShippingCountries', async () => { const instance = new ShopifyCheckoutSheet(); const invalidConfig = { ...acceleratedConfig, @@ -901,9 +901,9 @@ describe('ShopifyCheckoutSheetKit', () => { `'wallets.applePay.supportedShippingCountries' contains unexpected values. Expects ISO 3166-1 alpha-2 country codes (e.g., "US", "CA", "GB").`, ); - expect( + await expect( instance.configureAcceleratedCheckouts(invalidConfig as any), - ).toBe(false); + ).resolves.toBe(false); expect(console.error).toHaveBeenCalledWith( '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', expectedError, @@ -913,7 +913,7 @@ describe('ShopifyCheckoutSheetKit', () => { it('calls configureAcceleratedCheckouts with an empty array for supportShippingCountries when omitted', async () => { const instance = new ShopifyCheckoutSheet(); - instance.configureAcceleratedCheckouts({ + await instance.configureAcceleratedCheckouts({ ...acceleratedConfig, wallets: { applePay: { @@ -940,7 +940,7 @@ describe('ShopifyCheckoutSheetKit', () => { it('calls configureAcceleratedCheckouts with supportShippingCountries when given', async () => { const instance = new ShopifyCheckoutSheet(); - instance.configureAcceleratedCheckouts({ + await instance.configureAcceleratedCheckouts({ ...acceleratedConfig, wallets: { applePay: { @@ -967,25 +967,25 @@ describe('ShopifyCheckoutSheetKit', () => { }); describe('isAcceleratedCheckoutAvailable', () => { - it('calls native isAcceleratedCheckoutAvailable on iOS', () => { + it('calls native isAcceleratedCheckoutAvailable on iOS', async () => { const instance = new ShopifyCheckoutSheet(); NativeModule.isAcceleratedCheckoutAvailable.mockReturnValue(true); const result = instance.isAcceleratedCheckoutAvailable(); - expect(result).toBe(true); + await expect(result).resolves.toBe(true); expect( NativeModule.isAcceleratedCheckoutAvailable, ).toHaveBeenCalledTimes(1); }); - it('returns false on Android', () => { + it('returns false on Android', async () => { Platform.OS = 'android'; const instance = new ShopifyCheckoutSheet(); const result = instance.isAcceleratedCheckoutAvailable(); - expect(result).toBe(false); + await expect(result).resolves.toBe(false); expect( NativeModule.isAcceleratedCheckoutAvailable, ).not.toHaveBeenCalled(); diff --git a/sample/ios/Podfile.lock b/sample/ios/Podfile.lock index 07faf199..a63683ed 100644 --- a/sample/ios/Podfile.lock +++ b/sample/ios/Podfile.lock @@ -2578,7 +2578,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNShopifyCheckoutSheetKit (4.0.0): + - RNShopifyCheckoutSheetKit (3.9.0): - boost - DoubleConversion - fast_float @@ -2996,7 +2996,7 @@ SPEC CHECKSUMS: RNGestureHandler: eeb622199ef1fb3a076243131095df1c797072f0 RNReanimated: 237d420b7bb4378ef1dacc7d7a5c674fddb4b5d2 RNScreens: 3fc29af06302e1f1c18a7829fe57cbc2c0259912 - RNShopifyCheckoutSheetKit: 2a8c97d7780466538843d4cb1368c7ed76a33689 + RNShopifyCheckoutSheetKit: 5425cb8c6928b1aabe4504a8b37731d7e487cfd3 RNVectorIcons: be4d047a76ad307ffe54732208fb0498fcb8477f ShopifyCheckoutSheetKit: 5253ca4da4c4f31069286509693930d02b4150d8 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 diff --git a/sample/src/context/Cart.tsx b/sample/src/context/Cart.tsx index a3b15272..c4235be5 100644 --- a/sample/src/context/Cart.tsx +++ b/sample/src/context/Cart.tsx @@ -119,9 +119,9 @@ export const CartProvider: React.FC = ({children}) => { }, [cartId, fetchCart, setTotalQuantity]); const preloadCheckout = useCallback( - (checkoutURL: string) => { + async (checkoutURL: string) => { if (checkoutURL) { - const config = shopify.getConfig(); + const config = await shopify.getConfig(); if (config?.preloading) { shopify.preload(checkoutURL); } diff --git a/sample/src/screens/SettingsScreen.tsx b/sample/src/screens/SettingsScreen.tsx index 2d670204..5de4ce4c 100644 --- a/sample/src/screens/SettingsScreen.tsx +++ b/sample/src/screens/SettingsScreen.tsx @@ -101,8 +101,11 @@ function SettingsScreen() { const [preloadingEnabled, setPreloadingEnabled] = useState(false); useEffect(() => { - const config = shopify.getConfig(); - setPreloadingEnabled(config?.preloading ?? false); + async function loadConfig() { + const config = await shopify.getConfig(); + setPreloadingEnabled(config?.preloading ?? false); + } + loadConfig(); }, [shopify]); const handleColorSchemeChange = useCallback( @@ -116,8 +119,8 @@ function SettingsScreen() { [appConfig, setAppConfig, setColorScheme], ); - const handleTogglePreloading = useCallback(() => { - const currentConfig = shopify.getConfig(); + const handleTogglePreloading = useCallback(async () => { + const currentConfig = await shopify.getConfig(); const newPreloadingValue = !currentConfig?.preloading; shopify.setConfig({ ...currentConfig,