From c480ca8f5415cef4302862bc74d8c860c8b9633e Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Tue, 4 Aug 2026 18:19:17 +0100 Subject: [PATCH] Pin the Apple Pay merchant identifier per sample app Apple Pay rejects a payment request whose merchant identifier is absent from the entitlement of the app that sends it. Each sample registers a different identifier, so a single shared storefront config key cannot be correct for both apps at once. Each sample now states its identifier next to its own entitlement. The Swift demo defines it once in project.yml and the entitlement reads it back, so the runtime value and the entitlement cannot drift. React Native uses a constant matching its committed entitlements file. STOREFRONT_MERCHANT_IDENTIFIER is now inert, so it leaves demo.ejson. It still lingers in .env, .env.example and setup_storefront_env; the phase that rewrites those code paths removes it there. Co-Authored-By: Claude Opus 5 (1M context) --- .env.example | 6 +- config/secrets/demo.ejson | 4 +- platforms/react-native/sample/@types/env.d.ts | 2 +- .../project.pbxproj | 4 + .../CheckoutKitReactNativeDemo.entitlements | 2 +- .../react-native/sample/ios/Config.xcconfig | 2 + platforms/react-native/sample/src/App.tsx | 6 +- .../Sources/Lib/InfoDictionary.swift | 2 +- .../Samples/CheckoutKitSwiftDemo/project.yml | 4 +- scripts/setup_storefront_env | 58 +++++++-- scripts/test_setup_storefront_env | 117 ++++++++++++++++-- 11 files changed, 171 insertions(+), 36 deletions(-) create mode 100644 platforms/react-native/sample/ios/Config.xcconfig diff --git a/.env.example b/.env.example index 33060cd54..a909903d8 100644 --- a/.env.example +++ b/.env.example @@ -10,8 +10,10 @@ # Storefront details STOREFRONT_DOMAIN=your-store.myshopify.com STOREFRONT_ACCESS_TOKEN=your-public-storefront-access-token -# Optional Apple Pay merchant identifier used by accelerated checkout flows. -STOREFRONT_MERCHANT_IDENTIFIER= +# Optional Apple Pay merchant identifiers used by accelerated checkout flows. +# Will be read in sample apps as APPLE_PAY_MERCHANT_IDENTIFIER +REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER= +SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER= # Apple team that signs the Swift samples. Set your own team id, or the Xcode build # fails at signing. A blank value stays blank; setup applies no default team. diff --git a/config/secrets/demo.ejson b/config/secrets/demo.ejson index d7cb44edd..1c9bc5375 100644 --- a/config/secrets/demo.ejson +++ b/config/secrets/demo.ejson @@ -18,6 +18,8 @@ "LAST_NAME": "EJ[1:COcqDoNiIoRpNVpn5RtY+/98SiWXneivqVxsvjS2j1M=:Y6D/bjRLXox1Ev2x1wzwYkCcnSzyRqQr:Jy1SP2wB1gcl3EejYjG+BN7qguQeyEM=]", "PROVINCE": "EJ[1:COcqDoNiIoRpNVpn5RtY+/98SiWXneivqVxsvjS2j1M=:0TOrvPZDSNScmprg0GXGTCll0xiNg+sl:Y5zUNb4NN+0nUWUC/J1D0jjR]", "ZIP": "EJ[1:COcqDoNiIoRpNVpn5RtY+/98SiWXneivqVxsvjS2j1M=:i2R2JMndnE4CNZyGFxRrHQb9XJoHvGSP:wdAactHwHtDOTAhjdJ+wR3T2LRQ6IkI=]", - "PHONE": "EJ[1:COcqDoNiIoRpNVpn5RtY+/98SiWXneivqVxsvjS2j1M=:03PvHIo8P/rTDTomdb8wS+PH651lcbWY:j1idH9SHYOeh9iocqGY/wE689kiBKoRpATLD]" + "PHONE": "EJ[1:COcqDoNiIoRpNVpn5RtY+/98SiWXneivqVxsvjS2j1M=:03PvHIo8P/rTDTomdb8wS+PH651lcbWY:j1idH9SHYOeh9iocqGY/wE689kiBKoRpATLD]", + "REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER": "EJ[1:bhiOcRbAtZO6Hx3yvpCxPekCoORmQ1SLbS6EzjpoYEc=:tlKW/3ULel0N4o/SNSZJyE2IQ/FNZBfj:PmjIjdGhazyKgikizSAgKapRApjF13mhnTiV9emAmDYVj6qwQEuWIVYTtdxUAyYOwWnyXshLfNUHrDp829R46A==]", + "SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER": "EJ[1:bhiOcRbAtZO6Hx3yvpCxPekCoORmQ1SLbS6EzjpoYEc=:qvUsEPq8EDTo9jo0SHAZODzj+PIQ52WT:Q1lUWz8YLQj7Um9twGTRlzb78c+LCgzPnsTEFW0CKlGxEGtm1yGXnB5+bzD6EWlxjVAMvZdLsGZG2A==]" } } diff --git a/platforms/react-native/sample/@types/env.d.ts b/platforms/react-native/sample/@types/env.d.ts index aa80478fd..9bccafdf8 100644 --- a/platforms/react-native/sample/@types/env.d.ts +++ b/platforms/react-native/sample/@types/env.d.ts @@ -3,7 +3,7 @@ declare module '@env' { export const STOREFRONT_ACCESS_TOKEN: string; export const API_VERSION: string | undefined; export const STOREFRONT_VERSION: string | undefined; - export const STOREFRONT_MERCHANT_IDENTIFIER: string | undefined; + export const APPLE_PAY_MERCHANT_IDENTIFIER: string | undefined; export const EMAIL: string; export const ADDRESS_1: string; diff --git a/platforms/react-native/sample/ios/CheckoutKitReactNativeDemo.xcodeproj/project.pbxproj b/platforms/react-native/sample/ios/CheckoutKitReactNativeDemo.xcodeproj/project.pbxproj index 67d5a6fd9..5998b5f14 100644 --- a/platforms/react-native/sample/ios/CheckoutKitReactNativeDemo.xcodeproj/project.pbxproj +++ b/platforms/react-native/sample/ios/CheckoutKitReactNativeDemo.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ 6AF2B3762B0BCA6600C6CE4F /* CheckoutKitReactNativeDemo-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CheckoutKitReactNativeDemo-Bridging-Header.h"; sourceTree = ""; }; 6AFAD2D12BA9DEF8001F9644 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = CheckoutKitReactNativeDemo/Localizable.xcstrings; sourceTree = ""; }; 6AFC2CB62B0D5814003B5A63 /* libShopifyCheckoutKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libShopifyCheckoutKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 7A4F0E4A2E4B123400000001 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = CheckoutKitReactNativeDemo/LaunchScreen.storyboard; sourceTree = ""; }; 9E0AC43398437CA0E3D05DDF /* libPods-CheckoutKitReactNativeDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CheckoutKitReactNativeDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; ED728EF8BB9B5E9BA9D50EBE /* Pods-CheckoutKitReactNativeDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CheckoutKitReactNativeDemo.debug.xcconfig"; path = "Target Support Files/Pods-CheckoutKitReactNativeDemo/Pods-CheckoutKitReactNativeDemo.debug.xcconfig"; sourceTree = ""; }; @@ -88,6 +89,7 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + 7A4F0E4A2E4B123400000001 /* Config.xcconfig */, 6AB7C68F2AF197BE000DB11B /* Packages */, 13B07FAE1A68108700A75B9A /* CheckoutKitReactNativeDemo */, 832341AE1AAA6A7D00B99B32 /* Libraries */, @@ -456,6 +458,7 @@ }; 83CBBA201A601CBA00E9B192 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 7A4F0E4A2E4B123400000001 /* Config.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CC = ""; @@ -539,6 +542,7 @@ }; 83CBBA211A601CBA00E9B192 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 7A4F0E4A2E4B123400000001 /* Config.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CC = ""; diff --git a/platforms/react-native/sample/ios/CheckoutKitReactNativeDemo/CheckoutKitReactNativeDemo.entitlements b/platforms/react-native/sample/ios/CheckoutKitReactNativeDemo/CheckoutKitReactNativeDemo.entitlements index 9d2a9244e..a92816a9d 100644 --- a/platforms/react-native/sample/ios/CheckoutKitReactNativeDemo/CheckoutKitReactNativeDemo.entitlements +++ b/platforms/react-native/sample/ios/CheckoutKitReactNativeDemo/CheckoutKitReactNativeDemo.entitlements @@ -9,7 +9,7 @@ com.apple.developer.in-app-payments - merchant.com.shopify.checkoutkit.reactnativedemo + $(APPLE_PAY_MERCHANT_IDENTIFIER) diff --git a/platforms/react-native/sample/ios/Config.xcconfig b/platforms/react-native/sample/ios/Config.xcconfig new file mode 100644 index 000000000..8cd6af16a --- /dev/null +++ b/platforms/react-native/sample/ios/Config.xcconfig @@ -0,0 +1,2 @@ +APPLE_PAY_MERCHANT_IDENTIFIER = +#include? "Storefront.xcconfig" diff --git a/platforms/react-native/sample/src/App.tsx b/platforms/react-native/sample/src/App.tsx index 7088db8c7..bf5fe84f6 100644 --- a/platforms/react-native/sample/src/App.tsx +++ b/platforms/react-native/sample/src/App.tsx @@ -66,8 +66,8 @@ log('STOREFRONT_DOMAIN:', configured(env.STOREFRONT_DOMAIN)); log('STOREFRONT_ACCESS_TOKEN:', configured(env.STOREFRONT_ACCESS_TOKEN)); log('API_VERSION:', configured(storefrontApiVersion)); log( - 'STOREFRONT_MERCHANT_IDENTIFIER:', - configured(env.STOREFRONT_MERCHANT_IDENTIFIER), + 'APPLE_PAY_MERCHANT_IDENTIFIER:', + configured(env.APPLE_PAY_MERCHANT_IDENTIFIER), ); log('EMAIL:', configured(env.EMAIL)); log('PHONE:', configured(env.PHONE)); @@ -399,7 +399,7 @@ function AppWithCheckoutKit({children}: PropsWithChildren) { ApplePayContactField.email, ApplePayContactField.phone, ], - merchantIdentifier: env.STOREFRONT_MERCHANT_IDENTIFIER!, + merchantIdentifier: env.APPLE_PAY_MERCHANT_IDENTIFIER!, }, }, }, diff --git a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Lib/InfoDictionary.swift b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Lib/InfoDictionary.swift index 23b542334..548a0e608 100644 --- a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Lib/InfoDictionary.swift +++ b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Lib/InfoDictionary.swift @@ -35,7 +35,7 @@ final class InfoDictionary: Sendable { let phone = infoPlist["Phone"] as? String, let domain = infoPlist["StorefrontDomain"] as? String, let accessToken = infoPlist["StorefrontAccessToken"] as? String, - let merchantIdentifier = infoPlist["StorefrontMerchantIdentifier"] as? String, + let merchantIdentifier = infoPlist["ApplePayMerchantIdentifier"] as? String, let version = infoPlist["CFBundleShortVersionString"] as? String, let buildNumber = infoPlist["CFBundleVersion"] as? String else { diff --git a/platforms/swift/Samples/CheckoutKitSwiftDemo/project.yml b/platforms/swift/Samples/CheckoutKitSwiftDemo/project.yml index 8a2b08ef9..e517f5b98 100644 --- a/platforms/swift/Samples/CheckoutKitSwiftDemo/project.yml +++ b/platforms/swift/Samples/CheckoutKitSwiftDemo/project.yml @@ -84,7 +84,7 @@ targets: info: path: CheckoutKitSwiftDemo/Info.plist properties: - StorefrontMerchantIdentifier: $(STOREFRONT_MERCHANT_IDENTIFIER) + ApplePayMerchantIdentifier: $(APPLE_PAY_MERCHANT_IDENTIFIER) StorefrontDomain: $(STOREFRONT_DOMAIN) StorefrontAccessToken: $(STOREFRONT_ACCESS_TOKEN) CustomerAccountApiClientId: $(CUSTOMER_ACCOUNT_API_CLIENT_ID) @@ -132,7 +132,7 @@ targets: path: CheckoutKitSwiftDemo/CheckoutKitSwiftDemo.entitlements properties: com.apple.developer.in-app-payments: - - merchant.com.shopify.checkoutkit.swiftdemo + - $(APPLE_PAY_MERCHANT_IDENTIFIER) com.apple.developer.associated-domains: - webcredentials:$(STOREFRONT_DOMAIN)?mode=developer - applinks:$(STOREFRONT_DOMAIN)?mode=developer diff --git a/scripts/setup_storefront_env b/scripts/setup_storefront_env index d0f72088a..0e9461a93 100755 --- a/scripts/setup_storefront_env +++ b/scripts/setup_storefront_env @@ -9,6 +9,7 @@ ANDROID_ENV="${ROOT_DIR}/platforms/android/samples/CheckoutKitAndroidDemo/.env" SWIFT_DEMO_XCCONFIG="${ROOT_DIR}/platforms/swift/Samples/CheckoutKitSwiftDemo/Storefront.xcconfig" SWIFT_ACCELERATED_XCCONFIG="${ROOT_DIR}/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/Storefront.xcconfig" REACT_NATIVE_ENV="${ROOT_DIR}/platforms/react-native/sample/.env" +REACT_NATIVE_XCCONFIG="${ROOT_DIR}/platforms/react-native/sample/ios/Storefront.xcconfig" DEFAULT_API_VERSION="2026-04" DEFAULT_CUSTOMER_ACCOUNT_API_VERSION="2026-04" @@ -225,7 +226,8 @@ root_has_canonical_keys() { for key in \ STOREFRONT_DOMAIN \ STOREFRONT_ACCESS_TOKEN \ - STOREFRONT_MERCHANT_IDENTIFIER \ + REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER \ + SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER \ DEVELOPMENT_TEAM \ API_VERSION \ CUSTOMER_ACCOUNT_API_CLIENT_ID \ @@ -257,7 +259,8 @@ optional_values_need_prompt() { [[ "$prompt_optional_values" == "true" ]] && [[ -t 0 ]] && ( - [[ -z "$STOREFRONT_MERCHANT_IDENTIFIER_VALUE" ]] || + [[ -z "$REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE" ]] || + [[ -z "$SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE" ]] || [[ -z "$CUSTOMER_ACCOUNT_API_CLIENT_ID_VALUE" ]] || [[ -z "$CUSTOMER_ACCOUNT_API_SHOP_ID_VALUE" ]] ) @@ -267,10 +270,18 @@ fill_optional_values_from_environment() { local value local updated="false" - if [[ -z "$STOREFRONT_MERCHANT_IDENTIFIER_VALUE" ]]; then - value="$(env_fallback STOREFRONT_MERCHANT_IDENTIFIER)" + if [[ -z "$REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE" ]]; then + value="$(env_fallback REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER)" if [[ -n "$value" ]] && ! is_placeholder_value "$value"; then - STOREFRONT_MERCHANT_IDENTIFIER_VALUE="$value" + REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE="$value" + updated="true" + fi + fi + + if [[ -z "$SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE" ]]; then + value="$(env_fallback SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER)" + if [[ -n "$value" ]] && ! is_placeholder_value "$value"; then + SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE="$value" updated="true" fi fi @@ -418,10 +429,19 @@ load_values() { "$(read_env_value API_VERSION "$SWIFT_ACCELERATED_XCCONFIG")" \ "$DEFAULT_API_VERSION")" - STOREFRONT_MERCHANT_IDENTIFIER_VALUE="$(root_or_source_value STOREFRONT_MERCHANT_IDENTIFIER \ + REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE="$(root_or_source_value REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER \ + "$(env_fallback REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER)" \ + "$(read_env_value STOREFRONT_MERCHANT_IDENTIFIER "$ROOT_ENV")" \ "$(env_fallback STOREFRONT_MERCHANT_IDENTIFIER)" \ - "$(read_env_value STOREFRONT_MERCHANT_IDENTIFIER "$ANDROID_ENV")" \ + "$(read_env_value APPLE_PAY_MERCHANT_IDENTIFIER "$REACT_NATIVE_ENV")" \ "$(read_env_value STOREFRONT_MERCHANT_IDENTIFIER "$REACT_NATIVE_ENV")" \ + "$(read_env_value APPLE_PAY_MERCHANT_IDENTIFIER "$REACT_NATIVE_XCCONFIG")")" + + SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE="$(root_or_source_value SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER \ + "$(env_fallback SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER)" \ + "$(read_env_value STOREFRONT_MERCHANT_IDENTIFIER "$ROOT_ENV")" \ + "$(env_fallback STOREFRONT_MERCHANT_IDENTIFIER)" \ + "$(read_env_value APPLE_PAY_MERCHANT_IDENTIFIER "$SWIFT_DEMO_XCCONFIG")" \ "$(read_env_value STOREFRONT_MERCHANT_IDENTIFIER "$SWIFT_DEMO_XCCONFIG")")" CUSTOMER_ACCOUNT_API_CLIENT_ID_VALUE="$(root_or_source_value CUSTOMER_ACCOUNT_API_CLIENT_ID \ @@ -473,8 +493,12 @@ collect_missing_values() { STOREFRONT_ACCESS_TOKEN_VALUE="$(prompt_required "Storefront access token")" fi - if [[ "$prompt_optional_values" == "true" && -z "$STOREFRONT_MERCHANT_IDENTIFIER_VALUE" ]]; then - STOREFRONT_MERCHANT_IDENTIFIER_VALUE="$(prompt_optional "Apple Pay merchant identifier")" + if [[ "$prompt_optional_values" == "true" && -z "$REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE" ]]; then + REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE="$(prompt_optional "React Native Apple Pay merchant identifier")" + fi + + if [[ "$prompt_optional_values" == "true" && -z "$SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE" ]]; then + SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE="$(prompt_optional "Swift Apple Pay merchant identifier")" fi if [[ "$prompt_optional_values" == "true" && -z "$CUSTOMER_ACCOUNT_API_CLIENT_ID_VALUE" ]]; then @@ -509,8 +533,9 @@ generate_root_env() { EOF write_env_assignment STOREFRONT_DOMAIN "$STOREFRONT_DOMAIN_VALUE" write_env_assignment STOREFRONT_ACCESS_TOKEN "$STOREFRONT_ACCESS_TOKEN_VALUE" - echo "# Optional Apple Pay merchant identifier used by accelerated checkout flows." - write_env_assignment STOREFRONT_MERCHANT_IDENTIFIER "$STOREFRONT_MERCHANT_IDENTIFIER_VALUE" + echo "# Optional Apple Pay merchant identifiers used by accelerated checkout flows." + write_env_assignment REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER "$REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE" + write_env_assignment SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER "$SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER_VALUE" cat <"$SWIFT_DEMO_XCCONFIG" generate_swift_accelerated_xcconfig >"$SWIFT_ACCELERATED_XCCONFIG" generate_react_native_env >"$REACT_NATIVE_ENV" + generate_react_native_xcconfig >"$REACT_NATIVE_XCCONFIG" echo "Synced sample app storefront configuration files." } @@ -754,6 +785,7 @@ check_generated_files() { check_generated_file "$SWIFT_DEMO_XCCONFIG" generate_swift_demo_xcconfig || failed="true" check_generated_file "$SWIFT_ACCELERATED_XCCONFIG" generate_swift_accelerated_xcconfig || failed="true" check_generated_file "$REACT_NATIVE_ENV" generate_react_native_env || failed="true" + check_generated_file "$REACT_NATIVE_XCCONFIG" generate_react_native_xcconfig || failed="true" if [[ "$failed" == "true" ]]; then exit 1 diff --git a/scripts/test_setup_storefront_env b/scripts/test_setup_storefront_env index 50ced5973..c7c16a2e7 100755 --- a/scripts/test_setup_storefront_env +++ b/scripts/test_setup_storefront_env @@ -32,7 +32,7 @@ make_fixture() { mkdir -p \ "$fixture/scripts" \ "$fixture/platforms/android/samples/CheckoutKitAndroidDemo" \ - "$fixture/platforms/react-native/sample" \ + "$fixture/platforms/react-native/sample/ios" \ "$fixture/platforms/swift/Samples/CheckoutKitSwiftDemo" \ "$fixture/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp" @@ -50,14 +50,14 @@ assert_contains() { local path="$1" local pattern="$2" - grep -Fq "$pattern" "$path" || fail "expected generated file content was not found: $path: $pattern" + grep -Fq -- "$pattern" "$path" || fail "expected generated file content was not found: $path: $pattern" } assert_not_contains() { local path="$1" local pattern="$2" - if grep -Fq "$pattern" "$path"; then + if grep -Fq -- "$pattern" "$path"; then fail "unexpected generated file content was found" fi } @@ -70,6 +70,8 @@ assert_output_is_sanitized() { synthetic-store.example.myshopify.com \ synthetic-token \ synthetic-merchant \ + synthetic-react-native-merchant \ + synthetic-swift-merchant \ synthetic-client \ synthetic-shop \ migrated-store.example.myshopify.com \ @@ -82,6 +84,63 @@ assert_output_is_sanitized() { done } +test_merchant_identifier_propagates_to_sample_apps() { + local fixture output react_native_xcconfig swift_xcconfig + fixture="$(make_fixture)" + output="$fixture/output.log" + react_native_xcconfig="$fixture/platforms/react-native/sample/ios/Storefront.xcconfig" + swift_xcconfig="$fixture/platforms/swift/Samples/CheckoutKitSwiftDemo/Storefront.xcconfig" + + STOREFRONT_DOMAIN=synthetic-store.example.myshopify.com \ + STOREFRONT_ACCESS_TOKEN=synthetic-token \ + REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER=synthetic-react-native-merchant \ + SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER=synthetic-swift-merchant \ + "$fixture/scripts/setup_storefront_env" >"$output" 2>&1 + + assert_output_is_sanitized "$output" + assert_contains "$fixture/.env" \ + "REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER=synthetic-react-native-merchant" + assert_contains "$fixture/.env" \ + "SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER=synthetic-swift-merchant" + assert_contains "$fixture/platforms/react-native/sample/.env" \ + 'APPLE_PAY_MERCHANT_IDENTIFIER="synthetic-react-native-merchant"' + assert_contains "$react_native_xcconfig" \ + "APPLE_PAY_MERCHANT_IDENTIFIER = synthetic-react-native-merchant" + assert_contains "$swift_xcconfig" \ + "APPLE_PAY_MERCHANT_IDENTIFIER = synthetic-swift-merchant" +} + +test_sample_projects_read_generated_merchant_identifier() { + local demo_ejson react_native_app react_native_config react_native_entitlements react_native_project swift_project + demo_ejson="$REPO_ROOT/config/secrets/demo.ejson" + react_native_app="$REPO_ROOT/platforms/react-native/sample/src/App.tsx" + react_native_config="$REPO_ROOT/platforms/react-native/sample/ios/Config.xcconfig" + react_native_entitlements="$REPO_ROOT/platforms/react-native/sample/ios/CheckoutKitReactNativeDemo/CheckoutKitReactNativeDemo.entitlements" + react_native_project="$REPO_ROOT/platforms/react-native/sample/ios/CheckoutKitReactNativeDemo.xcodeproj/project.pbxproj" + swift_project="$REPO_ROOT/platforms/swift/Samples/CheckoutKitSwiftDemo/project.yml" + + jq -e '.environment | has("REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER")' \ + "$demo_ejson" >/dev/null || fail "demo.ejson is missing the React Native Apple Pay merchant identifier" + jq -e '.environment | has("SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER")' \ + "$demo_ejson" >/dev/null || fail "demo.ejson is missing the Swift Apple Pay merchant identifier" + assert_contains "$react_native_app" \ + "merchantIdentifier: env.APPLE_PAY_MERCHANT_IDENTIFIER!" + assert_not_contains "$react_native_app" \ + "merchant.com.shopify.checkoutkit.reactnativedemo" + assert_contains "$react_native_entitlements" \ + '$(APPLE_PAY_MERCHANT_IDENTIFIER)' + assert_not_contains "$react_native_entitlements" \ + "merchant.com.shopify.checkoutkit.reactnativedemo" + assert_contains "$react_native_config" "APPLE_PAY_MERCHANT_IDENTIFIER =" + assert_contains "$react_native_config" '#include? "Storefront.xcconfig"' + assert_contains "$react_native_project" "Config.xcconfig" + assert_contains "$swift_project" \ + 'ApplePayMerchantIdentifier: $(APPLE_PAY_MERCHANT_IDENTIFIER)' + assert_contains "$swift_project" \ + '- $(APPLE_PAY_MERCHANT_IDENTIFIER)' + assert_not_contains "$swift_project" "STOREFRONT_MERCHANT_IDENTIFIER" +} + test_sync_and_check() { local fixture output fixture="$(make_fixture)" @@ -89,7 +148,8 @@ test_sync_and_check() { STOREFRONT_DOMAIN=synthetic-store.example.myshopify.com \ STOREFRONT_ACCESS_TOKEN=synthetic-token \ - STOREFRONT_MERCHANT_IDENTIFIER=synthetic-merchant \ + REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER=synthetic-react-native-merchant \ + SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER=synthetic-swift-merchant \ CUSTOMER_ACCOUNT_API_CLIENT_ID=synthetic-client \ CUSTOMER_ACCOUNT_API_SHOP_ID=synthetic-shop \ "$fixture/scripts/setup_storefront_env" >"$output" 2>&1 @@ -100,10 +160,10 @@ test_sync_and_check() { assert_file_exists "$fixture/platforms/react-native/sample/.env" assert_file_exists "$fixture/platforms/swift/Samples/CheckoutKitSwiftDemo/Storefront.xcconfig" assert_file_exists "$fixture/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/Storefront.xcconfig" - assert_not_contains "$fixture/platforms/android/samples/CheckoutKitAndroidDemo/.env" "STOREFRONT_MERCHANT_IDENTIFIER" + assert_not_contains "$fixture/platforms/android/samples/CheckoutKitAndroidDemo/.env" "APPLE_PAY_MERCHANT_IDENTIFIER" assert_not_contains "$fixture/platforms/react-native/sample/.env" "CUSTOMER_ACCOUNT_API_VERSION" assert_not_contains "$fixture/platforms/swift/Samples/CheckoutKitSwiftDemo/Storefront.xcconfig" "CUSTOMER_ACCOUNT_API_VERSION" - assert_not_contains "$fixture/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/Storefront.xcconfig" "STOREFRONT_MERCHANT_IDENTIFIER" + assert_not_contains "$fixture/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/Storefront.xcconfig" "APPLE_PAY_MERCHANT_IDENTIFIER" assert_not_contains "$fixture/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/Storefront.xcconfig" "CUSTOMER_ACCOUNT_API_CLIENT_ID" assert_not_contains "$fixture/platforms/swift/Samples/ShopifyAcceleratedCheckoutsApp/Storefront.xcconfig" "EMAIL" @@ -145,7 +205,8 @@ test_required_values_only() { assert_contains "$fixture/platforms/android/samples/CheckoutKitAndroidDemo/.env" "STOREFRONT_DOMAIN=synthetic-store.example.myshopify.com" assert_contains "$fixture/platforms/react-native/sample/.env" "STOREFRONT_DOMAIN=\"synthetic-store.example.myshopify.com\"" assert_contains "$fixture/platforms/swift/Samples/CheckoutKitSwiftDemo/Storefront.xcconfig" "STOREFRONT_DOMAIN = synthetic-store.example.myshopify.com" - assert_contains "$fixture/.env" "STOREFRONT_MERCHANT_IDENTIFIER=" + assert_contains "$fixture/.env" "REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER=" + assert_contains "$fixture/.env" "SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER=" assert_contains "$fixture/.env" "CUSTOMER_ACCOUNT_API_CLIENT_ID=" assert_contains "$fixture/.env" "CUSTOMER_ACCOUNT_API_SHOP_ID=" assert_contains "$fixture/.env" "CUSTOMER_ACCOUNT_API_VERSION=2026-04" @@ -168,24 +229,52 @@ test_optional_sync_updates_blank_optional_values_after_required_setup() { "$fixture/scripts/setup_storefront_env" --skip-optional-prompts >"$output" 2>&1 assert_output_is_sanitized "$output" - assert_contains "$fixture/.env" "STOREFRONT_MERCHANT_IDENTIFIER=" + assert_contains "$fixture/.env" "REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER=" + assert_contains "$fixture/.env" "SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER=" assert_contains "$fixture/.env" "CUSTOMER_ACCOUNT_API_CLIENT_ID=" assert_contains "$fixture/.env" "CUSTOMER_ACCOUNT_API_SHOP_ID=" - STOREFRONT_MERCHANT_IDENTIFIER=synthetic-merchant \ + REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER=synthetic-react-native-merchant \ + SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER=synthetic-swift-merchant \ CUSTOMER_ACCOUNT_API_CLIENT_ID=synthetic-client \ CUSTOMER_ACCOUNT_API_SHOP_ID=synthetic-shop \ "$fixture/scripts/setup_storefront_env" >"$output" 2>&1 assert_output_is_sanitized "$output" - assert_contains "$fixture/.env" "STOREFRONT_MERCHANT_IDENTIFIER=synthetic-merchant" + assert_contains "$fixture/.env" "REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER=synthetic-react-native-merchant" + assert_contains "$fixture/.env" "SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER=synthetic-swift-merchant" assert_contains "$fixture/.env" "CUSTOMER_ACCOUNT_API_CLIENT_ID=synthetic-client" assert_contains "$fixture/.env" "CUSTOMER_ACCOUNT_API_SHOP_ID=synthetic-shop" - assert_contains "$fixture/platforms/react-native/sample/.env" "STOREFRONT_MERCHANT_IDENTIFIER=\"synthetic-merchant\"" + assert_contains "$fixture/platforms/react-native/sample/.env" "APPLE_PAY_MERCHANT_IDENTIFIER=\"synthetic-react-native-merchant\"" assert_contains "$fixture/platforms/swift/Samples/CheckoutKitSwiftDemo/Storefront.xcconfig" "CUSTOMER_ACCOUNT_API_CLIENT_ID = synthetic-client" assert_contains "$fixture/platforms/android/samples/CheckoutKitAndroidDemo/.env" "CUSTOMER_ACCOUNT_API_REDIRECT_URI=shop.synthetic-shop.app://callback" } +test_shared_merchant_identifier_migrates_to_app_specific_keys() { + local fixture output + fixture="$(make_fixture)" + output="$fixture/output.log" + + cat >"$fixture/.env" <<'EOF' +STOREFRONT_DOMAIN=synthetic-store.example.myshopify.com +STOREFRONT_ACCESS_TOKEN=synthetic-token +STOREFRONT_MERCHANT_IDENTIFIER=synthetic-merchant +EOF + + "$fixture/scripts/setup_storefront_env" --skip-optional-prompts >"$output" 2>&1 + assert_output_is_sanitized "$output" + + assert_not_contains "$fixture/.env" "STOREFRONT_MERCHANT_IDENTIFIER=" + assert_contains "$fixture/.env" \ + "REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER=synthetic-merchant" + assert_contains "$fixture/.env" \ + "SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER=synthetic-merchant" + assert_contains "$fixture/platforms/react-native/sample/.env" \ + 'APPLE_PAY_MERCHANT_IDENTIFIER="synthetic-merchant"' + assert_contains "$fixture/platforms/swift/Samples/CheckoutKitSwiftDemo/Storefront.xcconfig" \ + "APPLE_PAY_MERCHANT_IDENTIFIER = synthetic-merchant" +} + test_normalizes_existing_root_env() { local fixture output fixture="$(make_fixture)" @@ -219,7 +308,8 @@ test_blank_customer_account_api_version_defaults() { cat >"$fixture/.env" <<'EOF' STOREFRONT_DOMAIN=synthetic-store.example.myshopify.com STOREFRONT_ACCESS_TOKEN=synthetic-token -STOREFRONT_MERCHANT_IDENTIFIER= +REACT_NATIVE_APPLE_PAY_MERCHANT_IDENTIFIER= +SWIFT_APPLE_PAY_MERCHANT_IDENTIFIER= API_VERSION=2026-04 CUSTOMER_ACCOUNT_API_CLIENT_ID=synthetic-client CUSTOMER_ACCOUNT_API_SHOP_ID=synthetic-shop @@ -344,9 +434,12 @@ EOF assert_output_is_sanitized "$output" } +test_merchant_identifier_propagates_to_sample_apps +test_sample_projects_read_generated_merchant_identifier test_sync_and_check test_required_values_only test_optional_sync_updates_blank_optional_values_after_required_setup +test_shared_merchant_identifier_migrates_to_app_specific_keys test_normalizes_existing_root_env test_blank_customer_account_api_version_defaults test_development_team_follows_env_and_clears