diff --git a/.circleci/config.yml b/.circleci/config.yml index b0ff0a0ea3b..48553aeb0bc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -108,6 +108,7 @@ jobs: stripecore_tests \ stripeidentity_tests \ stripecardscan_tests \ + stripeapplepay_tests \ stripeuicore_tests \ ui_tests \ installation_cocoapods_without_frameworks_objc \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f907e90ac8..e1f221e5b2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * [Changed] The "save this card" checkbox in PaymentSheet is now unchecked by default. * [Fixed] Fixes issue that could cause symbol name collisions when using Objective-C * [Fixed] Fixes potential crash when using PaymentSheet with SwiftUI +* `Stripe` now requires `StripeApplePay`. See `MIGRATING.md` for more info. ### Identity ### Card scanning ### API diff --git a/Example/AppClipExample/AppClipExample (iOS).entitlements b/Example/AppClipExample/AppClipExample (iOS).entitlements new file mode 100644 index 00000000000..27be99cfbd6 --- /dev/null +++ b/Example/AppClipExample/AppClipExample (iOS).entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.in-app-payments + + merchant.com.stripetest.appclipexample + + + diff --git a/Example/AppClipExample/AppClipExample.xcodeproj/project.pbxproj b/Example/AppClipExample/AppClipExample.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..82b2daa91ce --- /dev/null +++ b/Example/AppClipExample/AppClipExample.xcodeproj/project.pbxproj @@ -0,0 +1,979 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 3168992B279A093F00FEBDE3 /* ApplePayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3168992A279A093F00FEBDE3 /* ApplePayModel.swift */; }; + 3168992D279A09A000FEBDE3 /* BackendModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3168992C279A09A000FEBDE3 /* BackendModel.swift */; }; + 3168992F279A0C1200FEBDE3 /* PaymentButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3168992E279A0C1200FEBDE3 /* PaymentButton.swift */; }; + 31689943279A123E00FEBDE3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 31689942279A123E00FEBDE3 /* Assets.xcassets */; }; + 31689946279A123E00FEBDE3 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 31689945279A123E00FEBDE3 /* Preview Assets.xcassets */; }; + 31689952279A123E00FEBDE3 /* AppClipExampleClipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31689951279A123E00FEBDE3 /* AppClipExampleClipTests.swift */; }; + 3168995C279A123E00FEBDE3 /* AppClipExampleClipUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3168995B279A123E00FEBDE3 /* AppClipExampleClipUITests.swift */; }; + 3168995E279A123F00FEBDE3 /* AppClipExampleClipUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3168995D279A123F00FEBDE3 /* AppClipExampleClipUITestsLaunchTests.swift */; }; + 31689961279A123F00FEBDE3 /* AppClipExampleClip.app in Embed App Clips */ = {isa = PBXBuildFile; fileRef = 3168993C279A123D00FEBDE3 /* AppClipExampleClip.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 3168996C279A125700FEBDE3 /* AppClipExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FBFDF32789135400AB706D /* AppClipExampleApp.swift */; }; + 3168996D279A125700FEBDE3 /* BackendModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3168992C279A09A000FEBDE3 /* BackendModel.swift */; }; + 3168996E279A125700FEBDE3 /* ApplePayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3168992A279A093F00FEBDE3 /* ApplePayModel.swift */; }; + 3168996F279A125700FEBDE3 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FBFDF42789135400AB706D /* ContentView.swift */; }; + 31689970279A125700FEBDE3 /* PaymentButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3168992E279A0C1200FEBDE3 /* PaymentButton.swift */; }; + 31689976279A16AD00FEBDE3 /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31689975279A16AD00FEBDE3 /* StripeCore.framework */; }; + 31689977279A16AD00FEBDE3 /* StripeCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 31689975279A16AD00FEBDE3 /* StripeCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 31689979279A16B200FEBDE3 /* StripeApplePay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31689978279A16B200FEBDE3 /* StripeApplePay.framework */; }; + 3168997A279A16B200FEBDE3 /* StripeApplePay.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 31689978279A16B200FEBDE3 /* StripeApplePay.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3168997C279A16B900FEBDE3 /* StripeApplePay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3168997B279A16B900FEBDE3 /* StripeApplePay.framework */; }; + 3168997D279A16B900FEBDE3 /* StripeApplePay.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3168997B279A16B900FEBDE3 /* StripeApplePay.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 31689980279A16BD00FEBDE3 /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3168997F279A16BD00FEBDE3 /* StripeCore.framework */; }; + 31689981279A16BD00FEBDE3 /* StripeCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3168997F279A16BD00FEBDE3 /* StripeCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 31FBFE0C2789135600AB706D /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FBFE0B2789135600AB706D /* Tests_iOS.swift */; }; + 31FBFE0E2789135600AB706D /* Tests_iOSLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FBFE0D2789135600AB706D /* Tests_iOSLaunchTests.swift */; }; + 31FBFE1B2789135600AB706D /* AppClipExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FBFDF32789135400AB706D /* AppClipExampleApp.swift */; }; + 31FBFE1D2789135700AB706D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FBFDF42789135400AB706D /* ContentView.swift */; }; + 31FBFE1F2789135700AB706D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 31FBFDF52789135600AB706D /* Assets.xcassets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 3168994E279A123E00FEBDE3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31FBFDEE2789135400AB706D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3168993B279A123D00FEBDE3; + remoteInfo = AppClipExampleClip; + }; + 31689958279A123E00FEBDE3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31FBFDEE2789135400AB706D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3168993B279A123D00FEBDE3; + remoteInfo = AppClipExampleClip; + }; + 3168995F279A123F00FEBDE3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31FBFDEE2789135400AB706D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3168993B279A123D00FEBDE3; + remoteInfo = AppClipExampleClip; + }; + 31FBFE082789135600AB706D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31FBFDEE2789135400AB706D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31FBFDF92789135600AB706D; + remoteInfo = "AppClipExample (iOS)"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 31689934279A0EA200FEBDE3 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 31689977279A16AD00FEBDE3 /* StripeCore.framework in Embed Frameworks */, + 3168997A279A16B200FEBDE3 /* StripeApplePay.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 31689965279A123F00FEBDE3 /* Embed App Clips */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/AppClips"; + dstSubfolderSpec = 16; + files = ( + 31689961279A123F00FEBDE3 /* AppClipExampleClip.app in Embed App Clips */, + ); + name = "Embed App Clips"; + runOnlyForDeploymentPostprocessing = 0; + }; + 3168997E279A16B900FEBDE3 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 31689981279A16BD00FEBDE3 /* StripeCore.framework in Embed Frameworks */, + 3168997D279A16B900FEBDE3 /* StripeApplePay.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 3168992A279A093F00FEBDE3 /* ApplePayModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplePayModel.swift; sourceTree = ""; }; + 3168992C279A09A000FEBDE3 /* BackendModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackendModel.swift; sourceTree = ""; }; + 3168992E279A0C1200FEBDE3 /* PaymentButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentButton.swift; sourceTree = ""; }; + 3168993C279A123D00FEBDE3 /* AppClipExampleClip.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppClipExampleClip.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 31689942279A123E00FEBDE3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 31689945279A123E00FEBDE3 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 31689947279A123E00FEBDE3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31689948279A123E00FEBDE3 /* AppClipExampleClip.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AppClipExampleClip.entitlements; sourceTree = ""; }; + 3168994D279A123E00FEBDE3 /* AppClipExampleClipTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppClipExampleClipTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 31689951279A123E00FEBDE3 /* AppClipExampleClipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppClipExampleClipTests.swift; sourceTree = ""; }; + 31689957279A123E00FEBDE3 /* AppClipExampleClipUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppClipExampleClipUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3168995B279A123E00FEBDE3 /* AppClipExampleClipUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppClipExampleClipUITests.swift; sourceTree = ""; }; + 3168995D279A123F00FEBDE3 /* AppClipExampleClipUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppClipExampleClipUITestsLaunchTests.swift; sourceTree = ""; }; + 31689975279A16AD00FEBDE3 /* StripeCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = StripeCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31689978279A16B200FEBDE3 /* StripeApplePay.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = StripeApplePay.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3168997B279A16B900FEBDE3 /* StripeApplePay.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = StripeApplePay.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3168997F279A16BD00FEBDE3 /* StripeCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = StripeCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31689982279A170100FEBDE3 /* AppClipExample (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "AppClipExample (iOS).entitlements"; sourceTree = ""; }; + 31FBFDF32789135400AB706D /* AppClipExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppClipExampleApp.swift; sourceTree = ""; }; + 31FBFDF42789135400AB706D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 31FBFDF52789135600AB706D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 31FBFDFA2789135600AB706D /* AppClipExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppClipExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 31FBFE072789135600AB706D /* Tests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 31FBFE0B2789135600AB706D /* Tests_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOS.swift; sourceTree = ""; }; + 31FBFE0D2789135600AB706D /* Tests_iOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOSLaunchTests.swift; sourceTree = ""; }; + 31FBFE172789135600AB706D /* Tests_macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOS.swift; sourceTree = ""; }; + 31FBFE192789135600AB706D /* Tests_macOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOSLaunchTests.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 31689939279A123D00FEBDE3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31689980279A16BD00FEBDE3 /* StripeCore.framework in Frameworks */, + 3168997C279A16B900FEBDE3 /* StripeApplePay.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3168994A279A123E00FEBDE3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31689954279A123E00FEBDE3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31FBFDF72789135600AB706D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31689976279A16AD00FEBDE3 /* StripeCore.framework in Frameworks */, + 31689979279A16B200FEBDE3 /* StripeApplePay.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31FBFE042789135600AB706D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 31689930279A0EA100FEBDE3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3168997F279A16BD00FEBDE3 /* StripeCore.framework */, + 3168997B279A16B900FEBDE3 /* StripeApplePay.framework */, + 31689978279A16B200FEBDE3 /* StripeApplePay.framework */, + 31689975279A16AD00FEBDE3 /* StripeCore.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 3168993D279A123D00FEBDE3 /* AppClipExampleClip */ = { + isa = PBXGroup; + children = ( + 31689942279A123E00FEBDE3 /* Assets.xcassets */, + 31689947279A123E00FEBDE3 /* Info.plist */, + 31689948279A123E00FEBDE3 /* AppClipExampleClip.entitlements */, + 31689944279A123E00FEBDE3 /* Preview Content */, + ); + path = AppClipExampleClip; + sourceTree = ""; + }; + 31689944279A123E00FEBDE3 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 31689945279A123E00FEBDE3 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 31689950279A123E00FEBDE3 /* AppClipExampleClipTests */ = { + isa = PBXGroup; + children = ( + 31689951279A123E00FEBDE3 /* AppClipExampleClipTests.swift */, + ); + path = AppClipExampleClipTests; + sourceTree = ""; + }; + 3168995A279A123E00FEBDE3 /* AppClipExampleClipUITests */ = { + isa = PBXGroup; + children = ( + 3168995B279A123E00FEBDE3 /* AppClipExampleClipUITests.swift */, + 3168995D279A123F00FEBDE3 /* AppClipExampleClipUITestsLaunchTests.swift */, + ); + path = AppClipExampleClipUITests; + sourceTree = ""; + }; + 31FBFDED2789135400AB706D = { + isa = PBXGroup; + children = ( + 31689982279A170100FEBDE3 /* AppClipExample (iOS).entitlements */, + 31FBFDF22789135400AB706D /* Shared */, + 31FBFE0A2789135600AB706D /* Tests iOS */, + 31FBFE162789135600AB706D /* Tests macOS */, + 3168993D279A123D00FEBDE3 /* AppClipExampleClip */, + 31689950279A123E00FEBDE3 /* AppClipExampleClipTests */, + 3168995A279A123E00FEBDE3 /* AppClipExampleClipUITests */, + 31FBFDFB2789135600AB706D /* Products */, + 31689930279A0EA100FEBDE3 /* Frameworks */, + ); + sourceTree = ""; + }; + 31FBFDF22789135400AB706D /* Shared */ = { + isa = PBXGroup; + children = ( + 31FBFDF32789135400AB706D /* AppClipExampleApp.swift */, + 3168992C279A09A000FEBDE3 /* BackendModel.swift */, + 3168992A279A093F00FEBDE3 /* ApplePayModel.swift */, + 31FBFDF42789135400AB706D /* ContentView.swift */, + 3168992E279A0C1200FEBDE3 /* PaymentButton.swift */, + 31FBFDF52789135600AB706D /* Assets.xcassets */, + ); + path = Shared; + sourceTree = ""; + }; + 31FBFDFB2789135600AB706D /* Products */ = { + isa = PBXGroup; + children = ( + 31FBFDFA2789135600AB706D /* AppClipExample.app */, + 31FBFE072789135600AB706D /* Tests iOS.xctest */, + 3168993C279A123D00FEBDE3 /* AppClipExampleClip.app */, + 3168994D279A123E00FEBDE3 /* AppClipExampleClipTests.xctest */, + 31689957279A123E00FEBDE3 /* AppClipExampleClipUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31FBFE0A2789135600AB706D /* Tests iOS */ = { + isa = PBXGroup; + children = ( + 31FBFE0B2789135600AB706D /* Tests_iOS.swift */, + 31FBFE0D2789135600AB706D /* Tests_iOSLaunchTests.swift */, + ); + path = "Tests iOS"; + sourceTree = ""; + }; + 31FBFE162789135600AB706D /* Tests macOS */ = { + isa = PBXGroup; + children = ( + 31FBFE172789135600AB706D /* Tests_macOS.swift */, + 31FBFE192789135600AB706D /* Tests_macOSLaunchTests.swift */, + ); + path = "Tests macOS"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 3168993B279A123D00FEBDE3 /* AppClipExampleClip */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31689962279A123F00FEBDE3 /* Build configuration list for PBXNativeTarget "AppClipExampleClip" */; + buildPhases = ( + 31689938279A123D00FEBDE3 /* Sources */, + 31689939279A123D00FEBDE3 /* Frameworks */, + 3168993A279A123D00FEBDE3 /* Resources */, + 3168997E279A16B900FEBDE3 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AppClipExampleClip; + productName = AppClipExampleClip; + productReference = 3168993C279A123D00FEBDE3 /* AppClipExampleClip.app */; + productType = "com.apple.product-type.application.on-demand-install-capable"; + }; + 3168994C279A123E00FEBDE3 /* AppClipExampleClipTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31689966279A123F00FEBDE3 /* Build configuration list for PBXNativeTarget "AppClipExampleClipTests" */; + buildPhases = ( + 31689949279A123E00FEBDE3 /* Sources */, + 3168994A279A123E00FEBDE3 /* Frameworks */, + 3168994B279A123E00FEBDE3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3168994F279A123E00FEBDE3 /* PBXTargetDependency */, + ); + name = AppClipExampleClipTests; + productName = AppClipExampleClipTests; + productReference = 3168994D279A123E00FEBDE3 /* AppClipExampleClipTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 31689956279A123E00FEBDE3 /* AppClipExampleClipUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31689969279A123F00FEBDE3 /* Build configuration list for PBXNativeTarget "AppClipExampleClipUITests" */; + buildPhases = ( + 31689953279A123E00FEBDE3 /* Sources */, + 31689954279A123E00FEBDE3 /* Frameworks */, + 31689955279A123E00FEBDE3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 31689959279A123E00FEBDE3 /* PBXTargetDependency */, + ); + name = AppClipExampleClipUITests; + productName = AppClipExampleClipUITests; + productReference = 31689957279A123E00FEBDE3 /* AppClipExampleClipUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; + 31FBFDF92789135600AB706D /* AppClipExample (iOS) */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31FBFE232789135700AB706D /* Build configuration list for PBXNativeTarget "AppClipExample (iOS)" */; + buildPhases = ( + 31FBFDF62789135600AB706D /* Sources */, + 31FBFDF72789135600AB706D /* Frameworks */, + 31FBFDF82789135600AB706D /* Resources */, + 31689934279A0EA200FEBDE3 /* Embed Frameworks */, + 31689965279A123F00FEBDE3 /* Embed App Clips */, + ); + buildRules = ( + ); + dependencies = ( + 31689960279A123F00FEBDE3 /* PBXTargetDependency */, + ); + name = "AppClipExample (iOS)"; + productName = "AppClipExample (iOS)"; + productReference = 31FBFDFA2789135600AB706D /* AppClipExample.app */; + productType = "com.apple.product-type.application"; + }; + 31FBFE062789135600AB706D /* Tests iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31FBFE292789135700AB706D /* Build configuration list for PBXNativeTarget "Tests iOS" */; + buildPhases = ( + 31FBFE032789135600AB706D /* Sources */, + 31FBFE042789135600AB706D /* Frameworks */, + 31FBFE052789135600AB706D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 31FBFE092789135600AB706D /* PBXTargetDependency */, + ); + name = "Tests iOS"; + productName = "Tests iOS"; + productReference = 31FBFE072789135600AB706D /* Tests iOS.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 31FBFDEE2789135400AB706D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1320; + LastUpgradeCheck = 1320; + TargetAttributes = { + 3168993B279A123D00FEBDE3 = { + CreatedOnToolsVersion = 13.2.1; + }; + 3168994C279A123E00FEBDE3 = { + CreatedOnToolsVersion = 13.2.1; + TestTargetID = 3168993B279A123D00FEBDE3; + }; + 31689956279A123E00FEBDE3 = { + CreatedOnToolsVersion = 13.2.1; + TestTargetID = 3168993B279A123D00FEBDE3; + }; + 31FBFDF92789135600AB706D = { + CreatedOnToolsVersion = 13.2.1; + }; + 31FBFE062789135600AB706D = { + CreatedOnToolsVersion = 13.2.1; + TestTargetID = 31FBFDF92789135600AB706D; + }; + }; + }; + buildConfigurationList = 31FBFDF12789135400AB706D /* Build configuration list for PBXProject "AppClipExample" */; + compatibilityVersion = "Xcode 12.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 31FBFDED2789135400AB706D; + productRefGroup = 31FBFDFB2789135600AB706D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 31FBFDF92789135600AB706D /* AppClipExample (iOS) */, + 31FBFE062789135600AB706D /* Tests iOS */, + 3168993B279A123D00FEBDE3 /* AppClipExampleClip */, + 3168994C279A123E00FEBDE3 /* AppClipExampleClipTests */, + 31689956279A123E00FEBDE3 /* AppClipExampleClipUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 3168993A279A123D00FEBDE3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31689946279A123E00FEBDE3 /* Preview Assets.xcassets in Resources */, + 31689943279A123E00FEBDE3 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3168994B279A123E00FEBDE3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31689955279A123E00FEBDE3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31FBFDF82789135600AB706D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31FBFE1F2789135700AB706D /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31FBFE052789135600AB706D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 31689938279A123D00FEBDE3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3168996C279A125700FEBDE3 /* AppClipExampleApp.swift in Sources */, + 3168996D279A125700FEBDE3 /* BackendModel.swift in Sources */, + 3168996E279A125700FEBDE3 /* ApplePayModel.swift in Sources */, + 3168996F279A125700FEBDE3 /* ContentView.swift in Sources */, + 31689970279A125700FEBDE3 /* PaymentButton.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31689949279A123E00FEBDE3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31689952279A123E00FEBDE3 /* AppClipExampleClipTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31689953279A123E00FEBDE3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3168995C279A123E00FEBDE3 /* AppClipExampleClipUITests.swift in Sources */, + 3168995E279A123F00FEBDE3 /* AppClipExampleClipUITestsLaunchTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31FBFDF62789135600AB706D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31FBFE1D2789135700AB706D /* ContentView.swift in Sources */, + 31FBFE1B2789135600AB706D /* AppClipExampleApp.swift in Sources */, + 3168992D279A09A000FEBDE3 /* BackendModel.swift in Sources */, + 3168992B279A093F00FEBDE3 /* ApplePayModel.swift in Sources */, + 3168992F279A0C1200FEBDE3 /* PaymentButton.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31FBFE032789135600AB706D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31FBFE0E2789135600AB706D /* Tests_iOSLaunchTests.swift in Sources */, + 31FBFE0C2789135600AB706D /* Tests_iOS.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 3168994F279A123E00FEBDE3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3168993B279A123D00FEBDE3 /* AppClipExampleClip */; + targetProxy = 3168994E279A123E00FEBDE3 /* PBXContainerItemProxy */; + }; + 31689959279A123E00FEBDE3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3168993B279A123D00FEBDE3 /* AppClipExampleClip */; + targetProxy = 31689958279A123E00FEBDE3 /* PBXContainerItemProxy */; + }; + 31689960279A123F00FEBDE3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3168993B279A123D00FEBDE3 /* AppClipExampleClip */; + targetProxy = 3168995F279A123F00FEBDE3 /* PBXContainerItemProxy */; + }; + 31FBFE092789135600AB706D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31FBFDF92789135600AB706D /* AppClipExample (iOS) */; + targetProxy = 31FBFE082789135600AB706D /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 31689963279A123F00FEBDE3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = AppClipExampleClip/AppClipExampleClip.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"AppClipExampleClip/Preview Content\""; + DEVELOPMENT_TEAM = Y28TH9SHX7; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = AppClipExampleClip/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = AppClipExample; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.AppClipExample.Clip; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31689964279A123F00FEBDE3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = AppClipExampleClip/AppClipExampleClip.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"AppClipExampleClip/Preview Content\""; + DEVELOPMENT_TEAM = Y28TH9SHX7; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = AppClipExampleClip/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = AppClipExample; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.AppClipExample.Clip; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 31689967279A123F00FEBDE3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.AppClipExample.AppClipExampleClipTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AppClipExampleClip.app/AppClipExampleClip"; + }; + name = Debug; + }; + 31689968279A123F00FEBDE3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.AppClipExample.AppClipExampleClipTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AppClipExampleClip.app/AppClipExampleClip"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 3168996A279A123F00FEBDE3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.AppClipExample.AppClipExampleClipUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = AppClipExampleClip; + }; + name = Debug; + }; + 3168996B279A123F00FEBDE3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.AppClipExample.AppClipExampleClipUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = AppClipExampleClip; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 31FBFE212789135700AB706D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_CODE_COVERAGE = NO; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_BITCODE = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 31FBFE222789135700AB706D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_CODE_COVERAGE = NO; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_BITCODE = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 31FBFE242789135700AB706D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "AppClipExample (iOS).entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = Y28TH9SHX7; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.AppClipExample; + PRODUCT_NAME = AppClipExample; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31FBFE252789135700AB706D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "AppClipExample (iOS).entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = Y28TH9SHX7; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.AppClipExample; + PRODUCT_NAME = AppClipExample; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 31FBFE2A2789135700AB706D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.Tests-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "AppClipExample (iOS)"; + }; + name = Debug; + }; + 31FBFE2B2789135700AB706D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.Tests-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "AppClipExample (iOS)"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 31689962279A123F00FEBDE3 /* Build configuration list for PBXNativeTarget "AppClipExampleClip" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31689963279A123F00FEBDE3 /* Debug */, + 31689964279A123F00FEBDE3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31689966279A123F00FEBDE3 /* Build configuration list for PBXNativeTarget "AppClipExampleClipTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31689967279A123F00FEBDE3 /* Debug */, + 31689968279A123F00FEBDE3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31689969279A123F00FEBDE3 /* Build configuration list for PBXNativeTarget "AppClipExampleClipUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3168996A279A123F00FEBDE3 /* Debug */, + 3168996B279A123F00FEBDE3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31FBFDF12789135400AB706D /* Build configuration list for PBXProject "AppClipExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31FBFE212789135700AB706D /* Debug */, + 31FBFE222789135700AB706D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31FBFE232789135700AB706D /* Build configuration list for PBXNativeTarget "AppClipExample (iOS)" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31FBFE242789135700AB706D /* Debug */, + 31FBFE252789135700AB706D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31FBFE292789135700AB706D /* Build configuration list for PBXNativeTarget "Tests iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31FBFE2A2789135700AB706D /* Debug */, + 31FBFE2B2789135700AB706D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 31FBFDEE2789135400AB706D /* Project object */; +} diff --git a/Example/AppClipExample/AppClipExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/AppClipExample/AppClipExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..919434a6254 --- /dev/null +++ b/Example/AppClipExample/AppClipExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Example/AppClipExample/AppClipExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/AppClipExample/AppClipExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/Example/AppClipExample/AppClipExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Example/AppClipExample/AppClipExampleClip/AppClipExampleClip.entitlements b/Example/AppClipExample/AppClipExampleClip/AppClipExampleClip.entitlements new file mode 100644 index 00000000000..062d9c8acd9 --- /dev/null +++ b/Example/AppClipExample/AppClipExampleClip/AppClipExampleClip.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.in-app-payments + + merchant.com.stripetest.appclipexample + + com.apple.developer.parent-application-identifiers + + $(AppIdentifierPrefix)com.stripe.AppClipExample + + + diff --git a/Example/AppClipExample/AppClipExampleClip/Assets.xcassets/AccentColor.colorset/Contents.json b/Example/AppClipExample/AppClipExampleClip/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000000..eb878970081 --- /dev/null +++ b/Example/AppClipExample/AppClipExampleClip/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/AppClipExample/AppClipExampleClip/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/AppClipExample/AppClipExampleClip/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..9221b9bb1a3 --- /dev/null +++ b/Example/AppClipExample/AppClipExampleClip/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/AppClipExample/AppClipExampleClip/Assets.xcassets/Contents.json b/Example/AppClipExample/AppClipExampleClip/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/Example/AppClipExample/AppClipExampleClip/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/AppClipExample/AppClipExampleClip/Info.plist b/Example/AppClipExample/AppClipExampleClip/Info.plist new file mode 100644 index 00000000000..074a6d1200e --- /dev/null +++ b/Example/AppClipExample/AppClipExampleClip/Info.plist @@ -0,0 +1,13 @@ + + + + + NSAppClip + + NSAppClipRequestEphemeralUserNotification + + NSAppClipRequestLocationConfirmation + + + + diff --git a/Example/AppClipExample/AppClipExampleClip/Preview Content/Preview Assets.xcassets/Contents.json b/Example/AppClipExample/AppClipExampleClip/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/Example/AppClipExample/AppClipExampleClip/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/AppClipExample/AppClipExampleClipTests/AppClipExampleClipTests.swift b/Example/AppClipExample/AppClipExampleClipTests/AppClipExampleClipTests.swift new file mode 100644 index 00000000000..86ae74525b3 --- /dev/null +++ b/Example/AppClipExample/AppClipExampleClipTests/AppClipExampleClipTests.swift @@ -0,0 +1,36 @@ +// +// AppClipExampleClipTests.swift +// AppClipExampleClipTests +// +// Created by David Estes on 1/20/22. +// + +import XCTest +@testable import AppClipExampleClip + +class AppClipExampleClipTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Example/AppClipExample/AppClipExampleClipUITests/AppClipExampleClipUITests.swift b/Example/AppClipExample/AppClipExampleClipUITests/AppClipExampleClipUITests.swift new file mode 100644 index 00000000000..db41fa74f2c --- /dev/null +++ b/Example/AppClipExample/AppClipExampleClipUITests/AppClipExampleClipUITests.swift @@ -0,0 +1,42 @@ +// +// AppClipExampleClipUITests.swift +// AppClipExampleClipUITests +// +// Created by David Estes on 1/20/22. +// + +import XCTest + +class AppClipExampleClipUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/Example/AppClipExample/AppClipExampleClipUITests/AppClipExampleClipUITestsLaunchTests.swift b/Example/AppClipExample/AppClipExampleClipUITests/AppClipExampleClipUITestsLaunchTests.swift new file mode 100644 index 00000000000..d84960f54c8 --- /dev/null +++ b/Example/AppClipExample/AppClipExampleClipUITests/AppClipExampleClipUITestsLaunchTests.swift @@ -0,0 +1,32 @@ +// +// AppClipExampleClipUITestsLaunchTests.swift +// AppClipExampleClipUITests +// +// Created by David Estes on 1/20/22. +// + +import XCTest + +class AppClipExampleClipUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} diff --git a/Example/AppClipExample/Shared/AppClipExampleApp.swift b/Example/AppClipExample/Shared/AppClipExampleApp.swift new file mode 100644 index 00000000000..8506e3e5bf5 --- /dev/null +++ b/Example/AppClipExample/Shared/AppClipExampleApp.swift @@ -0,0 +1,17 @@ +// +// AppClipExampleApp.swift +// Shared +// +// Created by David Estes on 1/7/22. +// + +import SwiftUI + +@main +struct AppClipExampleApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/Example/AppClipExample/Shared/ApplePayModel.swift b/Example/AppClipExample/Shared/ApplePayModel.swift new file mode 100644 index 00000000000..0f0cbad3597 --- /dev/null +++ b/Example/AppClipExample/Shared/ApplePayModel.swift @@ -0,0 +1,68 @@ +// +// ApplePayModel.swift +// IntegrationTester +// +// Created by David Estes on 2/18/21. +// + +import Foundation +import StripeApplePay +import PassKit + +class MyApplePayBackendModel : NSObject, ObservableObject, ApplePayContextDelegate { + @Published var paymentStatus: STPApplePayContext.PaymentStatus? + @Published var lastPaymentError: Error? + + func pay() { + // Configure a payment request + let pr = StripeAPI.paymentRequest(withMerchantIdentifier: "merchant.com.stripetest.appclipexample", country: "US", currency: "USD") + + // You'd generally want to configure at least `.postalAddress` here. + // We don't require anything here, as we don't want to enter an address + // in CI. + pr.requiredShippingContactFields = [] + pr.requiredBillingContactFields = [] + + // Configure shipping methods + let firstClassShipping = PKShippingMethod(label: "First Class Mail", amount: NSDecimalNumber(string: "10.99")) + firstClassShipping.detail = "Arrives in 3-5 days" + firstClassShipping.identifier = "firstclass" + let rocketRidesShipping = PKShippingMethod(label: "Rocket Rides courier", amount: NSDecimalNumber(string: "10.99")) + rocketRidesShipping.detail = "Arrives in 1-2 hours" + rocketRidesShipping.identifier = "rocketrides" + pr.shippingMethods = [ + firstClassShipping, + rocketRidesShipping + ] + + // Build payment summary items + // (You'll generally want to configure these based on the selected address and shipping method. + pr.paymentSummaryItems = [ + PKPaymentSummaryItem(label: "A very nice computer", amount: NSDecimalNumber(string: "19.99")), + PKPaymentSummaryItem(label: "Shipping", amount: NSDecimalNumber(string: "10.99")), + PKPaymentSummaryItem(label: "Stripe Computer Shop", amount: NSDecimalNumber(string: "29.99")) + ] + + // Present the Apple Pay Context: + let applePayContext = STPApplePayContext(paymentRequest: pr, delegate: self) + applePayContext?.presentApplePay() + } + + func applePayContext(_ context: STPApplePayContext, didCreatePaymentMethod paymentMethod: StripeAPI.PaymentMethod, paymentInformation: PKPayment, completion: @escaping IntentClientSecretCompletionBlock) { + // When the Apple Pay sheet is confirmed, create a PaymentIntent on your backend from the provided PKPayment information. + BackendModel.shared.fetchPaymentIntent() { secret in + if let clientSecret = secret { + // Call the completion block with the PaymentIntent's client secret. + completion(clientSecret, nil) + } else { + completion(nil, NSError()) + } + } + } + + func applePayContext(_ context: STPApplePayContext, didCompleteWith status: STPApplePayContext.PaymentStatus, error: Error?) { + // When the payment is complete, display the status. + self.paymentStatus = status + self.lastPaymentError = error + } +} diff --git a/Example/AppClipExample/Shared/Assets.xcassets/AccentColor.colorset/Contents.json b/Example/AppClipExample/Shared/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000000..eb878970081 --- /dev/null +++ b/Example/AppClipExample/Shared/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/AppClipExample/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/AppClipExample/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..c136eaff767 --- /dev/null +++ b/Example/AppClipExample/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,148 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/AppClipExample/Shared/Assets.xcassets/Contents.json b/Example/AppClipExample/Shared/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/Example/AppClipExample/Shared/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/AppClipExample/Shared/BackendModel.swift b/Example/AppClipExample/Shared/BackendModel.swift new file mode 100644 index 00000000000..404a8d9cc7e --- /dev/null +++ b/Example/AppClipExample/Shared/BackendModel.swift @@ -0,0 +1,67 @@ +// +// BackendModel.swift +// AppClipExample (iOS) +// +// Created by David Estes on 1/20/22. +// + +import Foundation +import StripeApplePay + +class BackendModel { + // You can replace this with your own backend URL. + // Visit https://glitch.com/edit/#!/stripe-integration-tester and click "remix". + static let backendAPIURL = URL(string: "https://stripe-integration-tester.glitch.me")! + + static let returnURL = "stp-integration-tester://stripe-redirect" + + public static let shared = BackendModel() + + func fetchPaymentIntent(completion: @escaping (String?) -> Void) { + let params = ["integration_method": "Apple Pay"] + getAPI(method: "create_pi", params: params) { (json) in + guard let paymentIntentClientSecret = json["paymentIntent"] as? String else { + completion(nil) + return + } + completion(paymentIntentClientSecret) + } + } + + func loadPublishableKey(completion: @escaping (String) -> Void) { + let params = ["integration_method": "Apple Pay"] + getAPI(method: "get_pub_key", params: params) { (json) in + if let publishableKey = json["publishableKey"] as? String { + completion(publishableKey) + } else { + assertionFailure("Could not fetch publishable key from backend") + } + } + } + + private func getAPI(method: String, params: [String : Any] = [:], completion: @escaping ([String : Any]) -> Void) { + var request = URLRequest(url: Self.backendAPIURL.appendingPathComponent(method)) + request.httpMethod = "POST" + + request.httpBody = try! JSONSerialization.data(withJSONObject: params, options: .prettyPrinted) + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + request.addValue("application/json", forHTTPHeaderField: "Accept") + + let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in + guard let unwrappedData = data, + let json = try? JSONSerialization.jsonObject(with: unwrappedData, options: []) as? [String : Any], + let publishableKey = json["publishableKey"] as? String else { + if let data = data { + print("\(String(decoding: data, as: UTF8.self))") + } else { + print("\(error ?? NSError())") + } + return + } + DispatchQueue.main.async { + completion(json) + } + }) + task.resume() + } +} diff --git a/Example/AppClipExample/Shared/ContentView.swift b/Example/AppClipExample/Shared/ContentView.swift new file mode 100644 index 00000000000..9005906dd63 --- /dev/null +++ b/Example/AppClipExample/Shared/ContentView.swift @@ -0,0 +1,63 @@ +// +// ContentView.swift +// Shared +// +// Created by David Estes on 1/7/22. +// + +import SwiftUI +import StripeApplePay + +struct ContentView: View { + @StateObject var model = MyApplePayBackendModel() + @State var ready = false + + var body: some View { + if ready { + VStack { + PaymentButton() { + model.pay() + } + .padding() + if let paymentStatus = model.paymentStatus { + PaymentStatusView(status: paymentStatus, lastPaymentError: model.lastPaymentError) + } + } + } else { + ProgressView().onAppear { + BackendModel.shared.loadPublishableKey { pubKey in + STPAPIClient.shared.publishableKey = pubKey + ready = true + } + } + } + } +} + +struct PaymentStatusView: View { + let status: STPApplePayContext.PaymentStatus + var lastPaymentError: Error? + + var body: some View { + HStack { + switch status { + case .success: + Image(systemName: "checkmark.circle.fill").foregroundColor(.green) + Text("Payment complete!") + case .error: + Image(systemName: "xmark.octagon.fill").foregroundColor(.red) + Text("Payment failed! \(lastPaymentError.debugDescription)") + case .userCancellation: + Image(systemName: "xmark.octagon.fill").foregroundColor(.orange) + Text("Payment canceled.") + } + } + .accessibility(identifier: "Payment status view") + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/Example/AppClipExample/Shared/PaymentButton.swift b/Example/AppClipExample/Shared/PaymentButton.swift new file mode 100644 index 00000000000..ef8436b1206 --- /dev/null +++ b/Example/AppClipExample/Shared/PaymentButton.swift @@ -0,0 +1,108 @@ +/* + Copyright © 2020 Apple 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. + +Abstract: +A button that hosts PKPaymentButton from Apple's Fruta example app. +*/ + +import SwiftUI +import PassKit + +struct PaymentButton: View { + var action: () -> Void + + var height: CGFloat { + #if os(macOS) + return 30 + #else + return 45 + #endif + } + + var body: some View { + Representable(action: action) + .frame(minWidth: 100, maxWidth: 400) + .frame(height: height) + .frame(maxWidth: .infinity) + .accessibility(label: Text("Buy with Apple Pay")) + } +} + +extension PaymentButton { + #if os(iOS) + typealias ViewRepresentable = UIViewRepresentable + #else + typealias ViewRepresentable = NSViewRepresentable + #endif + + struct Representable: ViewRepresentable { + var action: () -> Void + + init(action: @escaping () -> Void) { + self.action = action + } + + func makeCoordinator() -> Coordinator { + Coordinator(action: action) + } + + #if os(iOS) + func makeUIView(context: Context) -> UIView { + context.coordinator.button + } + + func updateUIView(_ rootView: UIView, context: Context) { + context.coordinator.action = action + } + #else + func makeNSView(context: Context) -> NSView { + context.coordinator.button + } + + func updateNSView(_ rootView: NSView, context: Context) { + context.coordinator.action = action + } + #endif + } + + class Coordinator: NSObject { + var action: () -> Void + var button = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .automatic) + + init(action: @escaping () -> Void) { + self.action = action + super.init() + #if os(iOS) + button.addTarget(self, action: #selector(callback(_:)), for: .touchUpInside) + #else + button.action = #selector(callback(_:)) + button.target = self + #endif + } + + @objc + func callback(_ sender: Any) { + action() + } + } +} + +struct PaymentButton_Previews: PreviewProvider { + static var previews: some View { + Group { + PaymentButton(action: {}) + .padding() + .preferredColorScheme(.light) + PaymentButton(action: {}) + .padding() + .preferredColorScheme(.dark) + } + .previewLayout(.sizeThatFits) + } +} diff --git a/Example/AppClipExample/Tests iOS/Tests_iOS.swift b/Example/AppClipExample/Tests iOS/Tests_iOS.swift new file mode 100644 index 00000000000..d6a4e8c8a1d --- /dev/null +++ b/Example/AppClipExample/Tests iOS/Tests_iOS.swift @@ -0,0 +1,42 @@ +// +// Tests_iOS.swift +// Tests iOS +// +// Created by David Estes on 1/7/22. +// + +import XCTest + +class Tests_iOS: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/Example/AppClipExample/Tests iOS/Tests_iOSLaunchTests.swift b/Example/AppClipExample/Tests iOS/Tests_iOSLaunchTests.swift new file mode 100644 index 00000000000..444f5836f84 --- /dev/null +++ b/Example/AppClipExample/Tests iOS/Tests_iOSLaunchTests.swift @@ -0,0 +1,32 @@ +// +// Tests_iOSLaunchTests.swift +// Tests iOS +// +// Created by David Estes on 1/7/22. +// + +import XCTest + +class Tests_iOSLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} diff --git a/Example/AppClipExample/Tests macOS/Tests_macOS.swift b/Example/AppClipExample/Tests macOS/Tests_macOS.swift new file mode 100644 index 00000000000..62a6187f96c --- /dev/null +++ b/Example/AppClipExample/Tests macOS/Tests_macOS.swift @@ -0,0 +1,42 @@ +// +// Tests_macOS.swift +// Tests macOS +// +// Created by David Estes on 1/7/22. +// + +import XCTest + +class Tests_macOS: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/Example/AppClipExample/Tests macOS/Tests_macOSLaunchTests.swift b/Example/AppClipExample/Tests macOS/Tests_macOSLaunchTests.swift new file mode 100644 index 00000000000..792219c1b0e --- /dev/null +++ b/Example/AppClipExample/Tests macOS/Tests_macOSLaunchTests.swift @@ -0,0 +1,32 @@ +// +// Tests_macOSLaunchTests.swift +// Tests macOS +// +// Created by David Estes on 1/7/22. +// + +import XCTest + +class Tests_macOSLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} diff --git a/Example/Basic Integration/Basic Integration.xcodeproj/project.pbxproj b/Example/Basic Integration/Basic Integration.xcodeproj/project.pbxproj index 5d5597173e6..600f5ede44e 100644 --- a/Example/Basic Integration/Basic Integration.xcodeproj/project.pbxproj +++ b/Example/Basic Integration/Basic Integration.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@ 04BC29A01CD81D3900318357 /* BrowseProductsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BC299F1CD81D3900318357 /* BrowseProductsViewController.swift */; }; 04D0761D1A69E66F00094431 /* Stripe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04D076191A69C14700094431 /* Stripe.framework */; }; 04D0761E1A69E66F00094431 /* Stripe.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 04D076191A69C14700094431 /* Stripe.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3137B69E2743808900CE7F5C /* StripeApplePay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B69D2743808900CE7F5C /* StripeApplePay.framework */; }; + 3137B69F2743808900CE7F5C /* StripeApplePay.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B69D2743808900CE7F5C /* StripeApplePay.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3143AB43230DF738003A54FC /* BasicIntegrationUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3143AB42230DF738003A54FC /* BasicIntegrationUITests.swift */; }; 31EFEE15258AE4110069BC5F /* Stripe3DS2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EFEE14258AE4110069BC5F /* Stripe3DS2.framework */; }; 31EFEE16258AE4110069BC5F /* Stripe3DS2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 31EFEE14258AE4110069BC5F /* Stripe3DS2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -50,6 +52,7 @@ files = ( E6AFFAFB26E9736F0067462F /* StripeUICore.framework in Embed Frameworks */, 362AA09C269E4F0B000DE28F /* StripeCore.framework in Embed Frameworks */, + 3137B69F2743808900CE7F5C /* StripeApplePay.framework in Embed Frameworks */, 04D0761E1A69E66F00094431 /* Stripe.framework in Embed Frameworks */, 31EFEE16258AE4110069BC5F /* Stripe3DS2.framework in Embed Frameworks */, ); @@ -104,6 +107,7 @@ files = ( E6AFFAFA26E9736F0067462F /* StripeUICore.framework in Frameworks */, 362AA09B269E4F02000DE28F /* StripeCore.framework in Frameworks */, + 3137B69E2743808900CE7F5C /* StripeApplePay.framework in Frameworks */, 04D0761D1A69E66F00094431 /* Stripe.framework in Frameworks */, 31EFEE15258AE4110069BC5F /* Stripe3DS2.framework in Frameworks */, ); diff --git a/Example/Non-Card Payment Examples/Non-Card Payment Examples.xcodeproj/project.pbxproj b/Example/Non-Card Payment Examples/Non-Card Payment Examples.xcodeproj/project.pbxproj index 29609655a0b..282a0481407 100644 --- a/Example/Non-Card Payment Examples/Non-Card Payment Examples.xcodeproj/project.pbxproj +++ b/Example/Non-Card Payment Examples/Non-Card Payment Examples.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 04533F191A688A0A00C7E52E /* Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = 04533F181A688A0A00C7E52E /* Constants.m */; }; 04D076171A69C11600094431 /* Stripe.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 04533F0C1A68812D00C7E52E /* Stripe.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 0FC7C95625B1127000E99D3E /* AfterpayClearpayExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FC7C95525B1127000E99D3E /* AfterpayClearpayExampleViewController.swift */; }; + 3137B6A42743809A00CE7F5C /* StripeApplePay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B6A32743809A00CE7F5C /* StripeApplePay.framework */; }; + 3137B6A52743809A00CE7F5C /* StripeApplePay.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B6A32743809A00CE7F5C /* StripeApplePay.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 316F810E254108F4000A80B5 /* OXXOExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 316F810D254108F4000A80B5 /* OXXOExampleViewController.m */; }; 316F8133254115F0000A80B5 /* PayPalExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 316F8132254115F0000A80B5 /* PayPalExampleViewController.m */; }; 3171D89A24DDECD80038A218 /* SofortExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3171D89924DDECD80038A218 /* SofortExampleViewController.m */; }; @@ -60,6 +62,7 @@ files = ( E6AFFB0126E973830067462F /* StripeUICore.framework in Embed Frameworks */, 362AA09F269E4F60000DE28F /* StripeCore.framework in Embed Frameworks */, + 3137B6A52743809A00CE7F5C /* StripeApplePay.framework in Embed Frameworks */, 04D076171A69C11600094431 /* Stripe.framework in Embed Frameworks */, 31EFEE19258AE4190069BC5F /* Stripe3DS2.framework in Embed Frameworks */, ); @@ -153,6 +156,7 @@ 04533F101A68813C00C7E52E /* PassKit.framework in Frameworks */, 04533F0D1A68812D00C7E52E /* Stripe.framework in Frameworks */, 31EFEE18258AE4190069BC5F /* Stripe3DS2.framework in Frameworks */, + 3137B6A42743809A00CE7F5C /* StripeApplePay.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/project.pbxproj b/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/project.pbxproj index 893c1190da0..87418a9c56c 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/project.pbxproj +++ b/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 31319EE425B11FA000C89E30 /* ExampleSwiftUIPaymentSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31319EE325B11FA000C89E30 /* ExampleSwiftUIPaymentSheet.swift */; }; 31319EE625B21DC200C89E30 /* ExampleSwiftUIViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31319EE525B21DC200C89E30 /* ExampleSwiftUIViews.swift */; }; 31319EE825B2328700C89E30 /* ExampleSwiftUICustomPaymentFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31319EE725B2328700C89E30 /* ExampleSwiftUICustomPaymentFlow.swift */; }; + 3137B6A12743808F00CE7F5C /* StripeApplePay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B6A02743808F00CE7F5C /* StripeApplePay.framework */; }; + 3137B6A22743808F00CE7F5C /* StripeApplePay.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B6A02743808F00CE7F5C /* StripeApplePay.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 31D8F79825BA0200004502D9 /* PaymentSheetUITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D8F79725BA0200004502D9 /* PaymentSheetUITest.swift */; }; 36002B4B25803B3100E46F8B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36002B4A25803B3100E46F8B /* SceneDelegate.swift */; }; 36595AE626B1F3C100634850 /* PaymentSheetLocalizationScreenshotGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36595AE526B1F3C100634850 /* PaymentSheetLocalizationScreenshotGenerator.swift */; }; @@ -58,6 +60,7 @@ files = ( E6AFFAFE26E9737B0067462F /* StripeUICore.framework in Embed Frameworks */, 36E5A2D325A79C94001668C9 /* Stripe3DS2.framework in Embed Frameworks */, + 3137B6A22743808F00CE7F5C /* StripeApplePay.framework in Embed Frameworks */, B6ECC7222696551800E4783E /* StripeCore.framework in Embed Frameworks */, B637077625107FBF0094499F /* Stripe.framework in Embed Frameworks */, ); @@ -202,6 +205,7 @@ files = ( E6AFFAFD26E9737B0067462F /* StripeUICore.framework in Frameworks */, B637077525107FBF0094499F /* Stripe.framework in Frameworks */, + 3137B6A12743808F00CE7F5C /* StripeApplePay.framework in Frameworks */, B6ECC7212696551800E4783E /* StripeCore.framework in Frameworks */, 36E5A2D225A79C7E001668C9 /* Stripe3DS2.framework in Frameworks */, ); diff --git a/Example/UI Examples/UI Examples.xcodeproj/project.pbxproj b/Example/UI Examples/UI Examples.xcodeproj/project.pbxproj index eb2c4d09971..de40b15f123 100644 --- a/Example/UI Examples/UI Examples.xcodeproj/project.pbxproj +++ b/Example/UI Examples/UI Examples.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 3137B69B2743808100CE7F5C /* StripeApplePay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B69A2743808100CE7F5C /* StripeApplePay.framework */; }; + 3137B69C2743808100CE7F5C /* StripeApplePay.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B69A2743808100CE7F5C /* StripeApplePay.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 31EFEE12258AE40A0069BC5F /* Stripe3DS2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EFEE11258AE40A0069BC5F /* Stripe3DS2.framework */; }; 31EFEE13258AE40A0069BC5F /* Stripe3DS2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 31EFEE11258AE40A0069BC5F /* Stripe3DS2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 36106222269E4D1100795224 /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36106221269E4D1100795224 /* StripeCore.framework */; }; @@ -36,6 +38,7 @@ files = ( E6AFFAF826E973670067462F /* StripeUICore.framework in Embed Frameworks */, 36106223269E4D2F00795224 /* StripeCore.framework in Embed Frameworks */, + 3137B69C2743808100CE7F5C /* StripeApplePay.framework in Embed Frameworks */, C19A55B11F1EC1ED00C8AABE /* Stripe.framework in Embed Frameworks */, 31EFEE13258AE40A0069BC5F /* Stripe3DS2.framework in Embed Frameworks */, ); @@ -82,6 +85,7 @@ files = ( E6AFFAF726E973670067462F /* StripeUICore.framework in Frameworks */, 36106222269E4D1100795224 /* StripeCore.framework in Frameworks */, + 3137B69B2743808100CE7F5C /* StripeApplePay.framework in Frameworks */, C19A55B01F1EC1ED00C8AABE /* Stripe.framework in Frameworks */, 31EFEE12258AE40A0069BC5F /* Stripe3DS2.framework in Frameworks */, ); diff --git a/IntegrationTester/IntegrationTester.xcodeproj/project.pbxproj b/IntegrationTester/IntegrationTester.xcodeproj/project.pbxproj index 78ce8f398fc..94afe681e22 100644 --- a/IntegrationTester/IntegrationTester.xcodeproj/project.pbxproj +++ b/IntegrationTester/IntegrationTester.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 312EB18225E48ACE00EB0BC3 /* PaymentMethodWithShippingInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312EB18125E48ACE00EB0BC3 /* PaymentMethodWithShippingInfoView.swift */; }; + 3137B6AA274380B100CE7F5C /* StripeApplePay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B6A9274380B100CE7F5C /* StripeApplePay.framework */; }; + 3137B6AB274380B100CE7F5C /* StripeApplePay.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B6A9274380B100CE7F5C /* StripeApplePay.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 313E26F925D4A20400D0162E /* BackendModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313E26F825D4A20400D0162E /* BackendModel.swift */; }; 314EF12025D47BB100BB2C70 /* CardSetupIntentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314EF11F25D47BB100BB2C70 /* CardSetupIntentsView.swift */; }; 317E053625D48E7A006D4C29 /* Stripe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 317E053525D48E7A006D4C29 /* Stripe.framework */; }; @@ -57,6 +59,7 @@ files = ( E6AFFB0726E973950067462F /* StripeUICore.framework in Embed Frameworks */, 362AA0A2269E4F9E000DE28F /* StripeCore.framework in Embed Frameworks */, + 3137B6AB274380B100CE7F5C /* StripeApplePay.framework in Embed Frameworks */, 317E053725D48E7A006D4C29 /* Stripe.framework in Embed Frameworks */, 317E053D25D48E84006D4C29 /* Stripe3DS2.framework in Embed Frameworks */, ); @@ -105,6 +108,7 @@ files = ( E6AFFB0626E973950067462F /* StripeUICore.framework in Frameworks */, 317E053625D48E7A006D4C29 /* Stripe.framework in Frameworks */, + 3137B6AA274380B100CE7F5C /* StripeApplePay.framework in Frameworks */, 362AA0A1269E4F8D000DE28F /* StripeCore.framework in Frameworks */, 317E053C25D48E84006D4C29 /* Stripe3DS2.framework in Frameworks */, ); diff --git a/LocalizationTester/LocalizationTester.xcodeproj/project.pbxproj b/LocalizationTester/LocalizationTester.xcodeproj/project.pbxproj index 63f8228a30d..b53d1786c14 100644 --- a/LocalizationTester/LocalizationTester.xcodeproj/project.pbxproj +++ b/LocalizationTester/LocalizationTester.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 31176A6A2565EB2D008864DC /* Stripe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31176A692565EB2D008864DC /* Stripe.framework */; }; 31176A6B2565EB2D008864DC /* Stripe.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 31176A692565EB2D008864DC /* Stripe.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 31176A6E2565EB59008864DC /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 31176A6D2565EB59008864DC /* ViewController.m */; }; + 3137B6A7274380A900CE7F5C /* StripeApplePay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B6A6274380A900CE7F5C /* StripeApplePay.framework */; }; + 3137B6A8274380A900CE7F5C /* StripeApplePay.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B6A6274380A900CE7F5C /* StripeApplePay.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 31EFEE1B258AE4210069BC5F /* Stripe3DS2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EFEE1A258AE4210069BC5F /* Stripe3DS2.framework */; }; 31EFEE1C258AE4210069BC5F /* Stripe3DS2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 31EFEE1A258AE4210069BC5F /* Stripe3DS2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 362AA0A4269E4FCA000DE28F /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 362AA0A3269E4FCA000DE28F /* StripeCore.framework */; }; @@ -44,6 +46,7 @@ files = ( E6AFFB0426E9738E0067462F /* StripeUICore.framework in Embed Frameworks */, 362AA0A5269E4FD1000DE28F /* StripeCore.framework in Embed Frameworks */, + 3137B6A8274380A900CE7F5C /* StripeApplePay.framework in Embed Frameworks */, 31176A6B2565EB2D008864DC /* Stripe.framework in Embed Frameworks */, 31EFEE1C258AE4210069BC5F /* Stripe3DS2.framework in Embed Frameworks */, ); @@ -125,6 +128,7 @@ files = ( E6AFFB0326E9738E0067462F /* StripeUICore.framework in Frameworks */, 362AA0A4269E4FCA000DE28F /* StripeCore.framework in Frameworks */, + 3137B6A7274380A900CE7F5C /* StripeApplePay.framework in Frameworks */, 31176A6A2565EB2D008864DC /* Stripe.framework in Frameworks */, 31EFEE1B258AE4210069BC5F /* Stripe3DS2.framework in Frameworks */, ); diff --git a/MIGRATING.md b/MIGRATING.md index 1cb43634d17..6f2bc72571f 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -1,5 +1,8 @@ ## Migration Guides +### Migrating from versions < X.X.X +* `Stripe` now requires `StripeApplePay`. If you are manually installing `Stripe`, you will need to include `StripeApplePay.xcframework`, which can be found in the [release assets](https://github.com/stripe/stripe-ios/releases/tag/x.x.x) for version x.x.x of the SDK. If you are using CocoaPods or Swift Package Manager, these dependencies will be imported automatically. + ### Migrating from versions < 21.10.0 * `StripeIdentity` now requires `StripeCameraCore`. If you are manually installing `StripeIdentity`, you will need to include `StripeCameraCore.xcframework`, which can be found in the [release assets](https://github.com/stripe/stripe-ios/releases/tag/21.10.0) for version 21.10.0 of the SDK. If you are using CocoaPods or Swift Package Manager, these dependencies will be imported automatically. diff --git a/Package.swift b/Package.swift index dd56ae3223d..565e70a23b7 100644 --- a/Package.swift +++ b/Package.swift @@ -12,6 +12,10 @@ let package = Package( name: "Stripe", targets: ["Stripe"] ), + .library( + name: "StripeApplePay", + targets: ["StripeApplePay"] + ), .library( name: "StripeIdentity", targets: ["StripeIdentity"] @@ -24,7 +28,7 @@ let package = Package( targets: [ .target( name: "Stripe", - dependencies: ["Stripe3DS2", "StripeCore", "StripeUICore"], + dependencies: ["Stripe3DS2", "StripeCore", "StripeApplePay", "StripeUICore"], path: "Stripe", exclude: ["Info.plist", "PanModal/LICENSE"], resources: [ @@ -59,6 +63,15 @@ let package = Package( .process("Info.plist") ] ), + .target( + name: "StripeApplePay", + dependencies: ["StripeCore"], + path: "StripeApplePay/StripeApplePay", + exclude: ["Info.plist"], + resources: [ + .process("Info.plist") + ] + ), .target( name: "StripeIdentity", dependencies: ["StripeCore", "StripeUICore", "StripeCameraCore"], diff --git a/Stripe.podspec b/Stripe.podspec index b147930939b..777e147bfd0 100644 --- a/Stripe.podspec +++ b/Stripe.podspec @@ -20,6 +20,7 @@ Pod::Spec.new do |s| s.ios.resource_bundle = { 'Stripe' => 'Stripe/Resources/**/*.{lproj,json,png,xcassets}' } s.dependency 'StripeCore', "#{s.version}" s.dependency 'StripeUICore', "#{s.version}" + s.dependency 'StripeApplePay', "#{s.version}" s.subspec 'Stripe3DS2' do |sp| sp.source_files = 'Stripe3DS2/Stripe3DS2/**/*.{h,m}' sp.resource_bundles = { 'Stripe3DS2' => ['Stripe3DS2/Stripe3DS2/Resources/**/*.{lproj,png}'] } diff --git a/Stripe.xcodeproj/project.pbxproj b/Stripe.xcodeproj/project.pbxproj index cd0652d9d2e..bc05f4affc9 100644 --- a/Stripe.xcodeproj/project.pbxproj +++ b/Stripe.xcodeproj/project.pbxproj @@ -24,12 +24,10 @@ 0FC7C95025B0EAF900E99D3E /* STPPaymentMethodAfterpayClearpayParamsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0FC7C94F25B0EAF900E99D3E /* STPPaymentMethodAfterpayClearpayParamsTest.m */; }; 0FC7C95325B1075D00E99D3E /* STPPaymentMethodAfterpayClearpayTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0FC7C95225B1075D00E99D3E /* STPPaymentMethodAfterpayClearpayTest.m */; }; 310AF468271A5151007339F4 /* StripeAPI+Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310AF467271A5151007339F4 /* StripeAPI+Deprecated.swift */; }; - 310AF46A271E071E007339F4 /* STPApplePayContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABDC025117C9B00CC59EF /* STPApplePayContext.swift */; }; 310AF46B271E0961007339F4 /* STPPaymentMethodCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABE0925117CA000CC59EF /* STPPaymentMethodCard.swift */; }; 310AF46C271E0DC2007339F4 /* PaymentSheetFlowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B684476A25538874005C4089 /* PaymentSheetFlowController.swift */; }; 310AF46D271E0E32007339F4 /* STPPaymentMethodCardWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABDB625117C9B00CC59EF /* STPPaymentMethodCardWallet.swift */; }; 310AF46E271E0E3C007339F4 /* PaymentSheet+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68A9E602582A82500E904B5 /* PaymentSheet+API.swift */; }; - 3111BE742512FD0800288D28 /* STPTelemetryClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD6A25117C9600CC59EF /* STPTelemetryClient.swift */; }; 3111BE792513039700288D28 /* STPShippingMethodTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD6C25117C9700CC59EF /* STPShippingMethodTableViewCell.swift */; }; 3111BE802513057C00288D28 /* STPMultiFormTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD9A25117C9900CC59EF /* STPMultiFormTextField.swift */; }; 3111BE852513075000288D28 /* STPFormTextFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABDE825117C9E00CC59EF /* STPFormTextFieldContainer.swift */; }; @@ -42,12 +40,10 @@ 3111C31125269B0700207E32 /* STPPaymentOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABDA725117C9A00CC59EF /* STPPaymentOption.swift */; }; 3111C3152526A53400207E32 /* STPSourceEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABE1425117CA000CC59EF /* STPSourceEnums.swift */; }; 3111C3172526A66000207E32 /* STPSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD2C25117C9300CC59EF /* STPSourceProtocol.swift */; }; - 3111C31A2526A74400207E32 /* PKPayment+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABE0E25117CA000CC59EF /* PKPayment+Stripe.swift */; }; 3111C31E2526A85C00207E32 /* PKPaymentAuthorizationViewController+Stripe_Blocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD5525117C9500CC59EF /* PKPaymentAuthorizationViewController+Stripe_Blocks.swift */; }; 3111C3222526A9C600207E32 /* PKAddPaymentPassRequest+Stripe_Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABDB925117C9B00CC59EF /* PKAddPaymentPassRequest+Stripe_Error.swift */; }; 3111C3262526AB2500207E32 /* NSString+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD9D25117C9900CC59EF /* NSString+Stripe.swift */; }; 3111C32F2526BE8600207E32 /* NSDecimalNumber+Stripe_Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD7225117C9700CC59EF /* NSDecimalNumber+Stripe_Currency.swift */; }; - 3111C3392526C24F00207E32 /* STPDispatchFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD7925117C9700CC59EF /* STPDispatchFunctions.swift */; }; 3111C33D2526C2A300207E32 /* STPPromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD8525117C9800CC59EF /* STPPromise.swift */; }; 3111C3522527871300207E32 /* STPCustomerContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABE1325117CA000CC59EF /* STPCustomerContext.swift */; }; 3111C35725279C3B00207E32 /* STPSwiftFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3111C35625279C3B00207E32 /* STPSwiftFixtures.swift */; }; @@ -55,7 +51,6 @@ 3111C3692527BC4200207E32 /* STPAddressFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABDFE25117C9F00CC59EF /* STPAddressFieldTableViewCell.swift */; }; 3111C3762527EB6800207E32 /* STPAuthenticationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABDFA25117C9F00CC59EF /* STPAuthenticationContext.swift */; }; 3111C3782527ECF900207E32 /* STPEphemeralKeyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABDB325117C9B00CC59EF /* STPEphemeralKeyProvider.swift */; }; - 3111C42C252BC7A300207E32 /* STPTelemetryClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3111C39C252BC78E00207E32 /* STPTelemetryClientTest.swift */; }; 3111C550252BCFFD00207E32 /* NSArray+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3111C411252BC7A100207E32 /* NSArray+StripeTest.swift */; }; 3111C552252BD00400207E32 /* NSDecimalNumber+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3111C3B3252BC79200207E32 /* NSDecimalNumber+StripeTest.swift */; }; 3111C555252BD8DD00207E32 /* NSDictionary+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3111C3B0252BC79100207E32 /* NSDictionary+StripeTest.swift */; }; @@ -172,6 +167,7 @@ 3137A74C26FD7710008BEF7B /* STPErrorBridgeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3137A74A26FD7710008BEF7B /* STPErrorBridgeTest.m */; }; 3137A74D26FD7AF5008BEF7B /* (null) in Sources */ = {isa = PBXBuildFile; }; 3137A74F26FE8754008BEF7B /* URLRequest+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3137A74E26FE8754008BEF7B /* URLRequest+StripeTest.swift */; }; + 3137B69727437F5A00CE7F5C /* StripeApplePay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B69627437F5A00CE7F5C /* StripeApplePay.framework */; }; 315E4B302639C86B00C1D955 /* STPIntentActionWeChatPayRedirectToApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315E4B2F2639C86B00C1D955 /* STPIntentActionWeChatPayRedirectToApp.swift */; }; 315E4B362639D80F00C1D955 /* STPIntentActionWeChatPayRedirectToAppTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315E4B352639D80F00C1D955 /* STPIntentActionWeChatPayRedirectToAppTest.swift */; }; 316E9F7926FA9FDF00986485 /* _stpobjc_STPAppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316E9F7826FA9FDF00986485 /* _stpobjc_STPAppInfo.swift */; }; @@ -186,7 +182,6 @@ 316F814525411879000A80B5 /* STPPaymentMethodPayPalTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 316F814425411879000A80B5 /* STPPaymentMethodPayPalTests.m */; }; 3171E93225783D1100F03DD1 /* CardScanningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3171E93125783D1100F03DD1 /* CardScanningView.swift */; }; 3176C2142519723B00300ADE /* STPPaymentCardTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABDCD25117C9C00CC59EF /* STPPaymentCardTextField.swift */; }; - 3176C21B251A64D400300ADE /* STPNumericStringValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABE1D25117CA100CC59EF /* STPNumericStringValidator.swift */; }; 3176C220251A679600300ADE /* STPKlarnaLineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD6D25117C9700CC59EF /* STPKlarnaLineItem.swift */; }; 3176C223251A696B00300ADE /* STPSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABDAA25117C9A00CC59EF /* STPSectionHeaderView.swift */; }; 3176C227251A6ABF00300ADE /* STPThreeDSButtonCustomization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD5825117C9500CC59EF /* STPThreeDSButtonCustomization.swift */; }; @@ -295,6 +290,7 @@ 3194CF5C2314869500E1940F /* STPPaymentMethodFPXTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3194CF5B2314869400E1940F /* STPPaymentMethodFPXTest.m */; }; 3194CF5E231487A200E1940F /* STPFPXBankBrandTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3194CF5D231487A100E1940F /* STPFPXBankBrandTest.m */; }; 319E3659271A03B300460867 /* STPAPIClient+Payments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD8625117C9800CC59EF /* STPAPIClient+Payments.swift */; }; + 319E82FA27431A3300F202D6 /* StripeApplePayTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 319E82F927431A3300F202D6 /* StripeApplePayTestUtils.framework */; }; 31A4381E251052C00067F936 /* FBSnapshotTestCase.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = F15AC18D1DBA9CA90009EADE /* FBSnapshotTestCase.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 31B49E9B26E8269C00A0464A /* _stpobjc_STPAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B49E9A26E8269C00A0464A /* _stpobjc_STPAPIClient.swift */; }; 31B49EA826E9743D00A0464A /* StripeCore+Import.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B49EA726E9743C00A0464A /* StripeCore+Import.swift */; }; @@ -333,11 +329,14 @@ 31E242FB26F9028400064908 /* STPCustomerContextTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E242FA26F9028400064908 /* STPCustomerContextTest.swift */; }; 31E242FD26FA3EBC00064908 /* _stpobjc_StripeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E242FC26FA3EBC00064908 /* _stpobjc_StripeAPI.swift */; }; 31E2430026FA428E00064908 /* STPApplePayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E242FE26FA424900064908 /* STPApplePayTest.swift */; }; + 31E6D95A2744408E00A89B6D /* StripeApplePay+Import.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D9592744408E00A89B6D /* StripeApplePay+Import.swift */; }; + 31E6D9782744722F00A89B6D /* _stpobjc_STPApplePayContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D9772744722F00A89B6D /* _stpobjc_STPApplePayContext.swift */; }; 31EFEE0E258AE3F40069BC5F /* Stripe3DS2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EFEE0D258AE3F40069BC5F /* Stripe3DS2.framework */; }; 31F2E875252403AD004D4B5E /* STPRedirectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABDBD25117C9B00CC59EF /* STPRedirectContext.swift */; }; 31F2E8782524143F004D4B5E /* STPPaymentResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD6225117C9600CC59EF /* STPPaymentResult.swift */; }; 31F2E87C25241C30004D4B5E /* STPApplePayPaymentOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABE2425117CA100CC59EF /* STPApplePayPaymentOption.swift */; }; 31F9015E26FEA07000F9E11D /* STPEphemeralKeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABD5E25117C9600CC59EF /* STPEphemeralKeyManager.swift */; }; + 31FBFDEA2788EAB400AB706D /* STPAPIClient+ApplePay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FBFDE92788EAB400AB706D /* STPAPIClient+ApplePay.swift */; }; 36002B782584251700E46F8B /* CheckboxButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36002B772584251700E46F8B /* CheckboxButton.swift */; }; 360C28A12567199100A6BF67 /* STPGenericInputTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360C28A02567199100A6BF67 /* STPGenericInputTextField.swift */; }; 3610621C2695199B00795224 /* STPPaymentMethodLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3610621B2695199B00795224 /* STPPaymentMethodLink.swift */; }; @@ -567,7 +566,6 @@ B649352226AB4AC900B8EC66 /* icon-pm-giropay@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B649351A26AB4AC900B8EC66 /* icon-pm-giropay@3x.png */; }; B649352326AB4AC900B8EC66 /* icon-pm-giropay_dark@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B649351B26AB4AC900B8EC66 /* icon-pm-giropay_dark@3x.png */; }; B649352426AB4AC900B8EC66 /* icon-pm-afterpay_dark@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B649351C26AB4AC900B8EC66 /* icon-pm-afterpay_dark@3x.png */; }; - B654D6F326570B71008F534A /* FraudDetectionData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B654D6F226570B70008F534A /* FraudDetectionData.swift */; }; B656292523E10AB100458A8E /* STPPaymentMethodBacsDebitTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B656292423E10AB100458A8E /* STPPaymentMethodBacsDebitTest.m */; }; B656293323E1139E00458A8E /* BacsDebitPaymentMethod.json in Resources */ = {isa = PBXBuildFile; fileRef = B656292623E10D4D00458A8E /* BacsDebitPaymentMethod.json */; }; B6596AB4255A282700F68826 /* WalletHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6596AB3255A282600F68826 /* WalletHeaderView.swift */; }; @@ -575,7 +573,6 @@ B6596AC2255C474D00F68826 /* CardDetailsEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6596AC1255C474D00F68826 /* CardDetailsEditView.swift */; }; B65E749525832A290080D9B3 /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65E749425832A290080D9B3 /* LoadingViewController.swift */; }; B65E749B258829550080D9B3 /* STPAnalyticsClient+PaymentSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65E749A258829550080D9B3 /* STPAnalyticsClient+PaymentSheet.swift */; }; - B660B312251D344700919002 /* STPAPIClient+ApplePay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317ABDCF25117C9C00CC59EF /* STPAPIClient+ApplePay.swift */; }; B6615A192564849100994B00 /* STPApplePayContext+PaymentSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6615A182564849100994B00 /* STPApplePayContext+PaymentSheet.swift */; }; B66B39B6223045EF006D1CAD /* CardPaymentMethod.json in Resources */ = {isa = PBXBuildFile; fileRef = B66B39B5223045EF006D1CAD /* CardPaymentMethod.json */; }; B66D5024222F5A27004A9210 /* STPPaymentMethodThreeDSecureUsageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B66D5023222F5A27004A9210 /* STPPaymentMethodThreeDSecureUsageTest.m */; }; @@ -674,7 +671,6 @@ B6E6C0E92655705100445507 /* Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E6C0E82655705100445507 /* Images.swift */; }; B6E6C0ED265583D400445507 /* ImageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E6C0EC265583D400445507 /* ImageTest.swift */; }; B6E8A9302657F51F00A48CC8 /* FraudDetectionDataTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E8A92F2657F51F00A48CC8 /* FraudDetectionDataTest.swift */; }; - B6E8A9332657F5CE00A48CC8 /* STPTelemetryClientFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E8A9322657F5CE00A48CC8 /* STPTelemetryClientFunctionalTest.swift */; }; B6E8A9362657F82C00A48CC8 /* UserDefaults+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E8A9352657F82C00A48CC8 /* UserDefaults+Stripe.swift */; }; B6E8A93E26580A6F00A48CC8 /* UserDefaults+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E8A93D26580A6F00A48CC8 /* UserDefaults+StripeTest.swift */; }; B6E9B916266B2EA500C1308D /* UIView+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E9B915266B2EA500C1308D /* UIView+Stripe.swift */; }; @@ -910,7 +906,6 @@ 3111C399252BC78E00207E32 /* STPApplePayFunctionalTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPApplePayFunctionalTest.swift; sourceTree = ""; }; 3111C39A252BC78E00207E32 /* STPPaymentMethodBancontactTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodBancontactTests.swift; sourceTree = ""; }; 3111C39B252BC78E00207E32 /* STPPaymentMethodTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodTest.swift; sourceTree = ""; }; - 3111C39C252BC78E00207E32 /* STPTelemetryClientTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPTelemetryClientTest.swift; sourceTree = ""; }; 3111C3A7252BC79000207E32 /* STPBECSDebitAccountNumberValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPBECSDebitAccountNumberValidatorTests.swift; sourceTree = ""; }; 3111C3A8252BC79000207E32 /* STPAUBECSFormViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPAUBECSFormViewModelTests.swift; sourceTree = ""; }; 3111C3A9252BC79000207E32 /* STPAPIClientTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPAPIClientTest.swift; sourceTree = ""; }; @@ -1072,7 +1067,6 @@ 317ABD6625117C9600CC59EF /* STPConnectAccountParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPConnectAccountParams.swift; sourceTree = ""; }; 317ABD6825117C9600CC59EF /* STPPaymentOptionTuple.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPPaymentOptionTuple.swift; sourceTree = ""; }; 317ABD6925117C9600CC59EF /* STPSetupIntentConfirmParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPSetupIntentConfirmParams.swift; sourceTree = ""; }; - 317ABD6A25117C9600CC59EF /* STPTelemetryClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPTelemetryClient.swift; sourceTree = ""; }; 317ABD6C25117C9700CC59EF /* STPShippingMethodTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPShippingMethodTableViewCell.swift; sourceTree = ""; }; 317ABD6D25117C9700CC59EF /* STPKlarnaLineItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPKlarnaLineItem.swift; sourceTree = ""; }; 317ABD6E25117C9700CC59EF /* STPCard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPCard.swift; sourceTree = ""; }; @@ -1086,7 +1080,6 @@ 317ABD7625117C9700CC59EF /* STPBECSDebitAccountNumberValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPBECSDebitAccountNumberValidator.swift; sourceTree = ""; }; 317ABD7725117C9700CC59EF /* STPBankAccountParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPBankAccountParams.swift; sourceTree = ""; }; 317ABD7825117C9700CC59EF /* STPPaymentMethodBancontact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodBancontact.swift; sourceTree = ""; }; - 317ABD7925117C9700CC59EF /* STPDispatchFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPDispatchFunctions.swift; sourceTree = ""; }; 317ABD7D25117C9700CC59EF /* STPSourceRedirect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPSourceRedirect.swift; sourceTree = ""; }; 317ABD7E25117C9800CC59EF /* STPSourceVerification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPSourceVerification.swift; sourceTree = ""; }; 317ABD7F25117C9800CC59EF /* STPSourceWeChatPayDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPSourceWeChatPayDetails.swift; sourceTree = ""; }; @@ -1140,7 +1133,6 @@ 317ABDBD25117C9B00CC59EF /* STPRedirectContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPRedirectContext.swift; sourceTree = ""; }; 317ABDBE25117C9B00CC59EF /* STPEmptyStripeResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPEmptyStripeResponse.swift; sourceTree = ""; }; 317ABDBF25117C9B00CC59EF /* STPValidatedTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPValidatedTextField.swift; sourceTree = ""; }; - 317ABDC025117C9B00CC59EF /* STPApplePayContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPApplePayContext.swift; sourceTree = ""; }; 317ABDC125117C9C00CC59EF /* STPPaymentMethodCardWalletVisaCheckout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodCardWalletVisaCheckout.swift; sourceTree = ""; }; 317ABDC225117C9C00CC59EF /* STPCardParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPCardParams.swift; sourceTree = ""; }; 317ABDC325117C9C00CC59EF /* STPPaymentMethodAUBECSDebitParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodAUBECSDebitParams.swift; sourceTree = ""; }; @@ -1153,7 +1145,6 @@ 317ABDCC25117C9C00CC59EF /* STPCardValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPCardValidator.swift; sourceTree = ""; }; 317ABDCD25117C9C00CC59EF /* STPPaymentCardTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPPaymentCardTextField.swift; sourceTree = ""; }; 317ABDCE25117C9C00CC59EF /* STPSetupIntentLastSetupError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPSetupIntentLastSetupError.swift; sourceTree = ""; }; - 317ABDCF25117C9C00CC59EF /* STPAPIClient+ApplePay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+ApplePay.swift"; sourceTree = ""; }; 317ABDD125117C9C00CC59EF /* STPFormEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPFormEncoder.swift; sourceTree = ""; }; 317ABDD225117C9D00CC59EF /* STPSourceKlarnaDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPSourceKlarnaDetails.swift; sourceTree = ""; }; 317ABDD325117C9D00CC59EF /* STPPaymentMethodSofortParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodSofortParams.swift; sourceTree = ""; }; @@ -1205,7 +1196,6 @@ 317ABE0B25117CA000CC59EF /* STPPaymentIntentShippingDetailsAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPPaymentIntentShippingDetailsAddress.swift; sourceTree = ""; }; 317ABE0C25117CA000CC59EF /* STPBankSelectionTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPBankSelectionTableViewCell.swift; sourceTree = ""; }; 317ABE0D25117CA000CC59EF /* STPCardScannerTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPCardScannerTableViewCell.swift; sourceTree = ""; }; - 317ABE0E25117CA000CC59EF /* PKPayment+Stripe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PKPayment+Stripe.swift"; sourceTree = ""; }; 317ABE0F25117CA000CC59EF /* STPThreeDSLabelCustomization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPThreeDSLabelCustomization.swift; sourceTree = ""; }; 317ABE1025117CA000CC59EF /* STPPaymentMethodBacsDebit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodBacsDebit.swift; sourceTree = ""; }; 317ABE1125117CA000CC59EF /* UIToolbar+Stripe_InputAccessory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIToolbar+Stripe_InputAccessory.swift"; sourceTree = ""; }; @@ -1217,7 +1207,6 @@ 317ABE1925117CA100CC59EF /* STPStringUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPStringUtils.swift; sourceTree = ""; }; 317ABE1B25117CA100CC59EF /* STPFakeAddPaymentPassViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPFakeAddPaymentPassViewController.swift; sourceTree = ""; }; 317ABE1C25117CA100CC59EF /* STPFPXBankBrand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPFPXBankBrand.swift; sourceTree = ""; }; - 317ABE1D25117CA100CC59EF /* STPNumericStringValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPNumericStringValidator.swift; sourceTree = ""; }; 317ABE1F25117CA100CC59EF /* UIImage+Stripe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Stripe.swift"; sourceTree = ""; }; 317ABE2025117CA100CC59EF /* STPUserInformation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPUserInformation.swift; sourceTree = ""; }; 317ABE2425117CA100CC59EF /* STPApplePayPaymentOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPApplePayPaymentOption.swift; sourceTree = ""; }; @@ -1299,7 +1288,10 @@ 31E242FA26F9028400064908 /* STPCustomerContextTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCustomerContextTest.swift; sourceTree = ""; }; 31E242FC26FA3EBC00064908 /* _stpobjc_StripeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _stpobjc_StripeAPI.swift; sourceTree = ""; }; 31E242FE26FA424900064908 /* STPApplePayTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPApplePayTest.swift; sourceTree = ""; }; + 31E6D9592744408E00A89B6D /* StripeApplePay+Import.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StripeApplePay+Import.swift"; sourceTree = ""; }; + 31E6D9772744722F00A89B6D /* _stpobjc_STPApplePayContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _stpobjc_STPApplePayContext.swift; sourceTree = ""; }; 31EFEE0D258AE3F40069BC5F /* Stripe3DS2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Stripe3DS2.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31FBFDE92788EAB400AB706D /* STPAPIClient+ApplePay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+ApplePay.swift"; sourceTree = ""; }; 36002B772584251700E46F8B /* CheckboxButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxButton.swift; sourceTree = ""; }; 36006C70244A4D8C002E7C41 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "Localizations/es-419.lproj/Localizable.strings"; sourceTree = ""; }; 36064BA026D6E6BC002A8AAA /* ms-MY */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ms-MY"; path = "Localizations/ms-MY.lproj/Localizable.strings"; sourceTree = ""; }; @@ -1522,7 +1514,6 @@ B649351A26AB4AC900B8EC66 /* icon-pm-giropay@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-pm-giropay@3x.png"; sourceTree = ""; }; B649351B26AB4AC900B8EC66 /* icon-pm-giropay_dark@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-pm-giropay_dark@3x.png"; sourceTree = ""; }; B649351C26AB4AC900B8EC66 /* icon-pm-afterpay_dark@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-pm-afterpay_dark@3x.png"; sourceTree = ""; }; - B654D6F226570B70008F534A /* FraudDetectionData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FraudDetectionData.swift; sourceTree = ""; }; B656292423E10AB100458A8E /* STPPaymentMethodBacsDebitTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentMethodBacsDebitTest.m; sourceTree = ""; }; B656292623E10D4D00458A8E /* BacsDebitPaymentMethod.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = BacsDebitPaymentMethod.json; sourceTree = ""; }; B6596AB3255A282600F68826 /* WalletHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletHeaderView.swift; sourceTree = ""; }; @@ -1582,7 +1573,6 @@ B6E6C0E82655705100445507 /* Images.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Images.swift; sourceTree = ""; }; B6E6C0EC265583D400445507 /* ImageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTest.swift; sourceTree = ""; }; B6E8A92F2657F51F00A48CC8 /* FraudDetectionDataTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FraudDetectionDataTest.swift; sourceTree = ""; }; - B6E8A9322657F5CE00A48CC8 /* STPTelemetryClientFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPTelemetryClientFunctionalTest.swift; sourceTree = ""; }; B6E8A9352657F82C00A48CC8 /* UserDefaults+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Stripe.swift"; sourceTree = ""; }; B6E8A93D26580A6F00A48CC8 /* UserDefaults+StripeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+StripeTest.swift"; sourceTree = ""; }; B6E9B915266B2EA500C1308D /* UIView+Stripe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Stripe.swift"; sourceTree = ""; }; @@ -1748,6 +1738,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 319E82FA27431A3300F202D6 /* StripeApplePayTestUtils.framework in Frameworks */, E6CDC468269E70590020A962 /* StripeCoreTestUtils.framework in Frameworks */, 04E01F7B21A8C37D0061402F /* OHHTTPStubs.xcframework in Frameworks */, 04E01F7C21A8C37D0061402F /* SWHttpTrafficRecorder.xcframework in Frameworks */, @@ -1768,6 +1759,7 @@ F116E94C1D83405E0026A52A /* Foundation.framework in Frameworks */, E6AFFAF126E970830067462F /* StripeUICore.framework in Frameworks */, 04533E7D1A6877F400C7E52E /* PassKit.framework in Frameworks */, + 3137B69727437F5A00CE7F5C /* StripeApplePay.framework in Frameworks */, F116E94D1D8340640026A52A /* Security.framework in Frameworks */, F1D64B2E1D87686E001CDB7C /* WebKit.framework in Frameworks */, ); @@ -2049,8 +2041,8 @@ 31B49E9C26E826A700A0464A /* Legacy Compatibility */, 3144E125253E7D3F00FE2605 /* Models */, 317ABD8625117C9800CC59EF /* STPAPIClient+Payments.swift */, - 317ABDCF25117C9C00CC59EF /* STPAPIClient+ApplePay.swift */, B61545FF2656A449006029D1 /* STPAPIClient+Radar.swift */, + 31FBFDE92788EAB400AB706D /* STPAPIClient+ApplePay.swift */, 317ABDB325117C9B00CC59EF /* STPEphemeralKeyProvider.swift */, 317ABD8825117C9800CC59EF /* STPPinManagementService.swift */, 317ABE0625117C9F00CC59EF /* STPPushProvisioningContext.swift */, @@ -2195,9 +2187,7 @@ isa = PBXGroup; children = ( E640A4C0265F26A500B31E91 /* Analytic+Payments.swift */, - B654D6F226570B70008F534A /* FraudDetectionData.swift */, E640A4BE265F269800B31E91 /* STPAnalyticsClient+Payments.swift */, - 317ABD6A25117C9600CC59EF /* STPTelemetryClient.swift */, ); name = Analytics; sourceTree = ""; @@ -2211,7 +2201,6 @@ 317ABDC625117C9C00CC59EF /* NSDictionary+Stripe.swift */, 317ABD9D25117C9900CC59EF /* NSString+Stripe.swift */, 317ABDB925117C9B00CC59EF /* PKAddPaymentPassRequest+Stripe_Error.swift */, - 317ABE0E25117CA000CC59EF /* PKPayment+Stripe.swift */, 317ABD5525117C9500CC59EF /* PKPaymentAuthorizationViewController+Stripe_Blocks.swift */, 317ABE0225117C9F00CC59EF /* UIBarButtonItem+Stripe.swift */, 367220B125DDC9D400708160 /* UIButton+Stripe.swift */, @@ -2239,9 +2228,7 @@ 317ABD2625117C9200CC59EF /* STPBINRange.swift */, 317ABD7425117C9700CC59EF /* STPBSBNumberValidator.swift */, 317ABD5425117C9500CC59EF /* STPColorUtils.swift */, - 317ABD7925117C9700CC59EF /* STPDispatchFunctions.swift */, 312D789A253DF824009224AF /* STPLocalizedString.swift */, - 317ABE1D25117CA100CC59EF /* STPNumericStringValidator.swift */, 317ABD8425117C9800CC59EF /* STPPhoneNumberValidator.swift */, 317ABDA825117C9A00CC59EF /* STPPostalCodeValidator.swift */, 317ABD8525117C9800CC59EF /* STPPromise.swift */, @@ -2407,7 +2394,6 @@ children = ( B684477A2555EA3B005C4089 /* DefaultPaymentMethodStore.swift */, B6E6C0E82655705100445507 /* Images.swift */, - 317ABDC025117C9B00CC59EF /* STPApplePayContext.swift */, 317ABDCA25117C9C00CC59EF /* STPBlocks.swift */, 317ABD2825117C9200CC59EF /* STPCardScanner.swift */, 317ABDCC25117C9C00CC59EF /* STPCardValidator.swift */, @@ -2563,8 +2549,10 @@ isa = PBXGroup; children = ( 31B49E9A26E8269C00A0464A /* _stpobjc_STPAPIClient.swift */, + 31E6D9772744722F00A89B6D /* _stpobjc_STPApplePayContext.swift */, 317ABDFD25117C9F00CC59EF /* _stpobjc_STPError.swift */, 316E9F7826FA9FDF00986485 /* _stpobjc_STPAppInfo.swift */, + 31E6D9592744408E00A89B6D /* StripeApplePay+Import.swift */, 31B49EA726E9743C00A0464A /* StripeCore+Import.swift */, 310AF467271A5151007339F4 /* StripeAPI+Deprecated.swift */, 31E242FC26FA3EBC00064908 /* _stpobjc_StripeAPI.swift */, @@ -3820,9 +3808,7 @@ 618E787B26EFDD310034A01F /* ServerErrorMapperTest.swift in Sources */, 31C5B87C252E859300A481A7 /* STPPaymentConfigurationTest.m in Sources */, 31C5B87E252E869D00A481A7 /* StripeErrorTest.swift in Sources */, - B6E8A9332657F5CE00A48CC8 /* STPTelemetryClientFunctionalTest.swift in Sources */, 36E283F2254A2FE20028C186 /* STPInputTextFieldValidatorTests.swift in Sources */, - 3111C42C252BC7A300207E32 /* STPTelemetryClientTest.swift in Sources */, 36AC3D0C2523F18300F252D7 /* STPIntentActionTypeTest.swift in Sources */, E60437F225D34316006E2E03 /* STPGenericInputPickerFieldSnapshotTests.swift in Sources */, D0EC87162790F72500CFACDC /* LinkWalletFooterViewSnapshotTests.swift in Sources */, @@ -3990,7 +3976,6 @@ D0BEB414273DE4360031D677 /* UINavigationBar+Link.swift in Sources */, B672438225252B43002E1AAF /* STPSourceVerification.swift in Sources */, 316F811625410B0E000A80B5 /* STPPaymentMethodOXXO.swift in Sources */, - 3176C21B251A64D400300ADE /* STPNumericStringValidator.swift in Sources */, 319490612514041300AD8F0B /* STPBankSelectionViewController.swift in Sources */, B67243912526378C002E1AAF /* STPSetupIntentConfirmParams.swift in Sources */, 3176C242251A6B5E00300ADE /* STPThreeDSUICustomization.swift in Sources */, @@ -4049,6 +4034,7 @@ B61546032656A749006029D1 /* STPRadarSession.swift in Sources */, 3176C220251A679600300ADE /* STPKlarnaLineItem.swift in Sources */, B648F38F25E45A780009FB36 /* PaymentOption+Images.swift in Sources */, + 31E6D95A2744408E00A89B6D /* StripeApplePay+Import.swift in Sources */, B6E9B916266B2EA500C1308D /* UIView+Stripe.swift in Sources */, 316F811A25410B12000A80B5 /* STPPaymentMethodOXXOParams.swift in Sources */, D075F87827443E3F00585EB8 /* SeparatorLabel.swift in Sources */, @@ -4063,6 +4049,7 @@ B65E749525832A290080D9B3 /* LoadingViewController.swift in Sources */, B6596AB4255A282700F68826 /* WalletHeaderView.swift in Sources */, B6926AE325267E6D001F208B /* STPAPIResponseDecodable.swift in Sources */, + 31FBFDEA2788EAB400AB706D /* STPAPIClient+ApplePay.swift in Sources */, B6E40EA9254253E400A5BABD /* PanModalPresentationAnimator.swift in Sources */, 36AC3D2C252521D700F252D7 /* STPPaymentIntentParams.swift in Sources */, B67243212524E514002E1AAF /* STPPaymentMethodSEPADebit.swift in Sources */, @@ -4100,7 +4087,6 @@ 3176C25D251A8FD300300ADE /* STPPushProvisioningContext.swift in Sources */, 3186371D25D1B51B00B31CF6 /* STPPaymentHandler.swift in Sources */, 3176C23A251A6B3A00300ADE /* STPThreeDSSelectionCustomization.swift in Sources */, - 3111BE742512FD0800288D28 /* STPTelemetryClient.swift in Sources */, 3111C30C252697A800207E32 /* STPPaymentMethodEnums.swift in Sources */, 3194908C2514484300AD8F0B /* STPUserInformation.swift in Sources */, 3111C2FE252688B800207E32 /* STPFakeAddPaymentPassViewController.swift in Sources */, @@ -4111,6 +4097,7 @@ 3111C3262526AB2500207E32 /* NSString+Stripe.swift in Sources */, B67243362524EF5E002E1AAF /* STPPaymentMethodThreeDSecureUsage.swift in Sources */, D060826326E91D920002D656 /* STPIntentActionBoletoDisplayDetails.swift in Sources */, + 31E6D9782744722F00A89B6D /* _stpobjc_STPApplePayContext.swift in Sources */, 3194906625140BEF00AD8F0B /* STPCoreViewController.swift in Sources */, 318321E125C864CB00D96469 /* STPPaymentConfirmation+SwiftUI.swift in Sources */, D0F2031F273C33C100AB10BF /* PayWithLinkViewController-SignUpViewController.swift in Sources */, @@ -4124,7 +4111,6 @@ B64519CC2514347A006BF25E /* STPPaymentMethodAddress.swift in Sources */, B672434B2524F56F002E1AAF /* STPSourceOwner.swift in Sources */, B6724354252514A0002E1AAF /* STPSourcePoller.swift in Sources */, - B660B312251D344700919002 /* STPAPIClient+ApplePay.swift in Sources */, 3111C31125269B0700207E32 /* STPPaymentOption.swift in Sources */, 31281BD02525725C00591A95 /* STPPaymentActivityIndicatorView.swift in Sources */, B6596AC2255C474D00F68826 /* CardDetailsEditView.swift in Sources */, @@ -4148,7 +4134,6 @@ B6E40EA1254253E400A5BABD /* PanModalPresentable.swift in Sources */, D0EC8710278E5D5E00CFACDC /* NSAttributedString+Stripe.swift in Sources */, 310AF468271A5151007339F4 /* StripeAPI+Deprecated.swift in Sources */, - 310AF46A271E071E007339F4 /* STPApplePayContext.swift in Sources */, D060826126E9125E0002D656 /* STPPaymentMethodBoleto.swift in Sources */, 31319EE125B11C8A00C89E30 /* PaymentSheet+SwiftUI.swift in Sources */, B6E40EAA254253E400A5BABD /* PanModalPresentationController.swift in Sources */, @@ -4327,7 +4312,6 @@ B6724305252419FD002E1AAF /* STPPaymentMethodGiropayParams.swift in Sources */, D0D28E622757398200098245 /* Button+Link.swift in Sources */, 3111BE802513057C00288D28 /* STPMultiFormTextField.swift in Sources */, - 3111C3392526C24F00207E32 /* STPDispatchFunctions.swift in Sources */, 319490592513CC6200AD8F0B /* STPImageLibrary.swift in Sources */, B6D9CEAB2514809B00AAD424 /* STPPaymentMethodCardNetworks.swift in Sources */, B67243502524F689002E1AAF /* STPSourceParams.swift in Sources */, @@ -4348,11 +4332,9 @@ 31D49B23251D75BA003FDB84 /* STPToken.swift in Sources */, B63A414425F9759900929729 /* STPPaymentMethodBLIKParams.swift in Sources */, B66F0CA426717B8C0097C2E8 /* PaymentSheetFormFactory.swift in Sources */, - B654D6F326570B71008F534A /* FraudDetectionData.swift in Sources */, 3194906C25140C0B00AD8F0B /* STPCoreScrollViewController.swift in Sources */, E69552EB25D1E4F000753FDA /* STPGenericInputPickerField.swift in Sources */, 3111C3152526A53400207E32 /* STPSourceEnums.swift in Sources */, - 3111C31A2526A74400207E32 /* PKPayment+Stripe.swift in Sources */, B6E40EA5254253E400A5BABD /* UIViewController+PanModalPresenter.swift in Sources */, 36DFC22B251BF21B003AC264 /* STPConnectAccountIndividualParams.swift in Sources */, 61EA8CEE26DD84DF00B2879D /* Error+PaymentSheet.swift in Sources */, diff --git a/Stripe.xcworkspace/contents.xcworkspacedata b/Stripe.xcworkspace/contents.xcworkspacedata index 97c7c79c15c..2e285f10e55 100644 --- a/Stripe.xcworkspace/contents.xcworkspacedata +++ b/Stripe.xcworkspace/contents.xcworkspacedata @@ -28,6 +28,9 @@ + + diff --git a/Stripe/Analytic+Payments.swift b/Stripe/Analytic+Payments.swift index 80129b87ccd..a531b17e05b 100644 --- a/Stripe/Analytic+Payments.swift +++ b/Stripe/Analytic+Payments.swift @@ -8,31 +8,7 @@ import Foundation @_spi(STP) import StripeCore - -/** - An analytic specific to payments that serializes a payment configuration into its params. - */ -protocol PaymentAnalytic: Analytic { - var paymentConfiguration: STPPaymentConfiguration? { get } - var productUsage: Set { get } - var additionalParams: [String: Any] { get } -} - -extension PaymentAnalytic { - var params: [String: Any] { - var params = additionalParams - - if let paymentConfiguration = paymentConfiguration { - let configurationDictionary = STPAnalyticsClient.serializeConfiguration(paymentConfiguration) - params = params.merging(configurationDictionary) { (_, new) in new } - } - - params["ui_usage_level"] = STPAnalyticsClient.uiUsageLevelString(from: productUsage) - params["apple_pay_enabled"] = NSNumber(value: StripeAPI.deviceSupportsApplePay()) - params["ocr_type"] = STPAnalyticsClient.ocrTypeString() - return params - } -} +@_spi(STP) import StripeApplePay /** A generic analytic type. @@ -54,3 +30,21 @@ struct GenericPaymentErrorAnalytic: PaymentAnalytic, ErrorAnalytic { let additionalParams: [String : Any] let error: AnalyticLoggableError } + + +extension GenericPaymentAnalytic { + var params: [String: Any] { + var params = additionalParams + + if let paymentConfiguration = paymentConfiguration { + let configurationDictionary = STPAnalyticsClient.serializeConfiguration(paymentConfiguration) + params = params.merging(configurationDictionary) { (_, new) in new } + } + + params["ui_usage_level"] = STPAnalyticsClient.uiUsageLevelString(from: productUsage) + params["apple_pay_enabled"] = NSNumber(value: StripeAPI.deviceSupportsApplePay()) + params["ocr_type"] = STPAnalyticsClient.ocrTypeString() + params["pay_var"] = STPAnalyticsClient.paymentsSDKVariant + return params + } +} diff --git a/Stripe/STPAPIClient+ApplePay.swift b/Stripe/STPAPIClient+ApplePay.swift index 4499ccde623..49aa3cce499 100644 --- a/Stripe/STPAPIClient+ApplePay.swift +++ b/Stripe/STPAPIClient+ApplePay.swift @@ -8,6 +8,7 @@ import Foundation import PassKit @_spi(STP) import StripeCore +@_spi(STP) import StripeApplePay /// STPAPIClient extensions to create Stripe Tokens, Sources, or PaymentMethods from Apple Pay PKPayment objects. extension STPAPIClient { @@ -17,7 +18,7 @@ extension STPAPIClient { /// - completion: The callback to run with the returned Stripe token (and any errors that may have occurred). public func createToken(with payment: PKPayment, completion: @escaping STPTokenCompletionBlock) { - var params = parameters(for: payment) + var params = payment.stp_tokenParameters(apiClient: self) STPTelemetryClient.shared.addTelemetryFields(toParams: ¶ms) createToken( withParameters: params, @@ -68,32 +69,6 @@ extension STPAPIClient { } - /// Converts Stripe errors into the appropriate Apple Pay error, for use in `PKPaymentAuthorizationResult`. - /// If the error can be fixed by the customer within the Apple Pay sheet, we return an NSError that can be displayed in the Apple Pay sheet. - /// Otherwise, the original error is returned, resulting in the Apple Pay sheet being dismissed. You should display the error message to the customer afterwards. - /// Currently, we convert billing address related errors into a PKPaymentError that helpfully points to the billing address field in the Apple Pay sheet. - /// Note that Apple Pay should prevent most card errors (e.g. invalid CVC, expired cards) when you add a card to the wallet. - /// - Parameter stripeError: An error from the Stripe SDK. - public class func pkPaymentError(forStripeError stripeError: Error?) -> Error? { - guard let stripeError = stripeError else { - return nil - } - - if (stripeError as NSError).domain == STPError.stripeDomain - && ((stripeError as NSError).userInfo[STPError.cardErrorCodeKey] as? String - == STPCardErrorCode.incorrectZip.rawValue) - { - var userInfo = (stripeError as NSError).userInfo - var errorCode: PKPaymentError.Code = .unknownError - errorCode = .billingContactInvalidError - userInfo[PKPaymentErrorKey.postalAddressUserInfoKey.rawValue] = - CNPostalAddressPostalCodeKey - return NSError( - domain: STPError.stripeDomain, code: errorCode.rawValue, userInfo: userInfo) - } - return stripeError - } - class func billingDetails(from payment: PKPayment) -> STPPaymentMethodBillingDetails? { var billingDetails: STPPaymentMethodBillingDetails? if payment.billingContact != nil { @@ -136,60 +111,4 @@ extension STPAPIClient { return billingDetails } - - class func addressParams(from contact: PKContact?) -> [AnyHashable: Any]? { - guard let contact = contact else { - return nil - } - var params: [AnyHashable: Any] = [:] - let stpAddress = STPAddress(pkContact: contact) - - params["name"] = stpAddress.name - params["address_line1"] = stpAddress.line1 - params["address_city"] = stpAddress.city - params["address_state"] = stpAddress.state - params["address_zip"] = stpAddress.postalCode - params["address_country"] = stpAddress.country - - return params - } - - func parameters(for payment: PKPayment) -> [String: Any] { - let paymentString = String(data: payment.token.paymentData, encoding: .utf8) - var payload: [String: Any] = [:] - payload["pk_token"] = paymentString - if let billingContact = payment.billingContact { - payload["card"] = STPAPIClient.addressParams(from: billingContact) - } - - assert( - !((paymentString?.count ?? 0) == 0 - && self.publishableKey?.hasPrefix("pk_live") ?? false), - "The pk_token is empty. Using Apple Pay with an iOS Simulator while not in Stripe Test Mode will always fail." - ) - - let paymentInstrumentName = payment.token.paymentMethod.displayName - if let paymentInstrumentName = paymentInstrumentName { - payload["pk_token_instrument_name"] = paymentInstrumentName - } - - let paymentNetwork = payment.token.paymentMethod.network - if let paymentNetwork = paymentNetwork { - // Note: As of SDK 20.0.0, this will return `PKPaymentNetwork(_rawValue: MasterCard)`. - // We're intentionally leaving it this way: See RUN_MOBILESDK-125. - payload["pk_token_payment_network"] = paymentNetwork - } - - var transactionIdentifier = payment.token.transactionIdentifier - if transactionIdentifier != "" { - if payment.stp_isSimulated() { - transactionIdentifier = PKPayment.stp_testTransactionIdentifier() ?? "" - } - payload["pk_token_transaction_id"] = transactionIdentifier - } - - return payload - } - - // MARK: - Errors } diff --git a/Stripe/STPAPIClient+Payments.swift b/Stripe/STPAPIClient+Payments.swift index db722028e45..5e6cd396356 100644 --- a/Stripe/STPAPIClient+Payments.swift +++ b/Stripe/STPAPIClient+Payments.swift @@ -10,6 +10,7 @@ import Foundation import PassKit import UIKit @_spi(STP) import StripeCore +@_spi(STP) import StripeApplePay #if canImport(Stripe3DS2) import Stripe3DS2 diff --git a/Stripe/STPAPIClient+Radar.swift b/Stripe/STPAPIClient+Radar.swift index 2239b803e82..439d3351a2c 100644 --- a/Stripe/STPAPIClient+Radar.swift +++ b/Stripe/STPAPIClient+Radar.swift @@ -8,6 +8,7 @@ import Foundation @_spi(STP) import StripeCore +@_spi(STP) import StripeApplePay private let APIEndpointRadarSession = "radar/session" diff --git a/Stripe/STPAUBECSFormViewModel.swift b/Stripe/STPAUBECSFormViewModel.swift index 994e9035c52..0978414cb2d 100644 --- a/Stripe/STPAUBECSFormViewModel.swift +++ b/Stripe/STPAUBECSFormViewModel.swift @@ -8,6 +8,7 @@ import Foundation import UIKit +@_spi(STP) import StripeCore @_spi(STP) import StripeUICore enum STPAUBECSFormViewField: Int { diff --git a/Stripe/STPAddress.swift b/Stripe/STPAddress.swift index 7ff82a09718..5a5c2949718 100644 --- a/Stripe/STPAddress.swift +++ b/Stripe/STPAddress.swift @@ -10,6 +10,7 @@ import Contacts import Foundation import PassKit @_spi(STP) import StripeUICore +@_spi(STP) import StripeCore /// What set of billing address information you need to collect from your user. /// @@ -479,14 +480,3 @@ extension STPAddress: NSCopying { return copyAddress } } - -func stringIfHasContentsElseNil(_ string: String?) -> // MARK: - - String? -{ - guard let string = string, - !string.isEmpty - else { - return nil - } - return string -} diff --git a/Stripe/STPAddressViewModel.swift b/Stripe/STPAddressViewModel.swift index ff5a99ff22f..df502b56894 100644 --- a/Stripe/STPAddressViewModel.swift +++ b/Stripe/STPAddressViewModel.swift @@ -9,6 +9,7 @@ import Contacts import CoreLocation import UIKit +@_spi(STP) import StripeCore protocol STPAddressViewModelDelegate: AnyObject { func addressViewModelDidChange(_ addressViewModel: STPAddressViewModel) diff --git a/Stripe/STPAnalyticsClient+PaymentSheet.swift b/Stripe/STPAnalyticsClient+PaymentSheet.swift index 71b2a5c231a..5329d763b3c 100644 --- a/Stripe/STPAnalyticsClient+PaymentSheet.swift +++ b/Stripe/STPAnalyticsClient+PaymentSheet.swift @@ -8,6 +8,7 @@ import Foundation @_spi(STP) import StripeCore +@_spi(STP) import StripeApplePay extension STPAnalyticsClient { // MARK: - Log events diff --git a/Stripe/STPAnalyticsClient+Payments.swift b/Stripe/STPAnalyticsClient+Payments.swift index bde6d628125..1347f609a51 100644 --- a/Stripe/STPAnalyticsClient+Payments.swift +++ b/Stripe/STPAnalyticsClient+Payments.swift @@ -90,17 +90,6 @@ extension STPAnalyticsClient { } return uiUsageLevel } - - class func ocrTypeString() -> String { - if #available(iOS 13.0, macCatalyst 14.0, *) { - if STPAnalyticsClient.sharedClient.productUsage.contains( - STPCardScanner.stp_analyticsIdentifier) - { - return "stripe" - } - } - return "none" - } } // MARK: - Creation diff --git a/Stripe/STPBECSDebitAccountNumberValidator.swift b/Stripe/STPBECSDebitAccountNumberValidator.swift index b687775ba06..7153f460513 100644 --- a/Stripe/STPBECSDebitAccountNumberValidator.swift +++ b/Stripe/STPBECSDebitAccountNumberValidator.swift @@ -7,6 +7,7 @@ // import Foundation +@_spi(STP) import StripeCore class STPBECSDebitAccountNumberValidator: STPNumericStringValidator { class func validationState( diff --git a/Stripe/STPBSBNumberValidator.swift b/Stripe/STPBSBNumberValidator.swift index 81e1e194a24..7c5b0855eb7 100644 --- a/Stripe/STPBSBNumberValidator.swift +++ b/Stripe/STPBSBNumberValidator.swift @@ -8,6 +8,7 @@ import Foundation import UIKit +@_spi(STP) import StripeCore @_spi(STP) import StripeUICore class STPBSBNumberValidator: STPNumericStringValidator { diff --git a/Stripe/STPBlocks.swift b/Stripe/STPBlocks.swift index 943f7879a23..2fb86f3384f 100644 --- a/Stripe/STPBlocks.swift +++ b/Stripe/STPBlocks.swift @@ -34,10 +34,19 @@ import PassKit case error /// The user cancelled the payment (for example, by hitting "cancel" in the Apple Pay dialog). case userCancellation + + init(applePayStatus: STPApplePayContext.PaymentStatus) { + switch applePayStatus { + case .success: + self = .success + case .error: + self = .error + case .userCancellation: + self = .userCancellation + } + } } -/// An empty block, called with no arguments, returning nothing. -public typealias STPVoidBlock = () -> Void /// A block that may optionally be called with an error. /// - Parameter error: The error that occurred, if any. public typealias STPErrorBlock = (Error?) -> Void @@ -143,11 +152,6 @@ typealias STPFPXBankStatusCompletionBlock = (STPFPXBankStatusResponse?, Error?) /// A block called with a payment status and an optional error. /// - Parameter error: The error that occurred, if any. public typealias STPPaymentStatusBlock = (STPPaymentStatus, Error?) -> Void -/// A block to be run with the client secret of a PaymentIntent or SetupIntent. -/// - Parameters: -/// - clientSecret: The client secret of the PaymentIntent or SetupIntent. See https://stripe.com/docs/api/payment_intents/object#payment_intent_object-client_secret -/// - error: The error that occurred when creating the Intent, or nil if none occurred. -public typealias STPIntentClientSecretCompletionBlock = (String?, Error?) -> Void /** A callback to be run with an STPRadarSession diff --git a/Stripe/STPCardCVCInputTextFieldFormatter.swift b/Stripe/STPCardCVCInputTextFieldFormatter.swift index e5eddf104a4..4ea3ae13dae 100644 --- a/Stripe/STPCardCVCInputTextFieldFormatter.swift +++ b/Stripe/STPCardCVCInputTextFieldFormatter.swift @@ -7,6 +7,7 @@ // import UIKit +@_spi(STP) import StripeCore class STPCardCVCInputTextFieldFormatter: STPNumericDigitInputTextFormatter { diff --git a/Stripe/STPCardExpiryInputTextFieldValidator.swift b/Stripe/STPCardExpiryInputTextFieldValidator.swift index d50936cdd82..c92d9f421c0 100644 --- a/Stripe/STPCardExpiryInputTextFieldValidator.swift +++ b/Stripe/STPCardExpiryInputTextFieldValidator.swift @@ -7,6 +7,7 @@ // import UIKit +@_spi(STP) import StripeCore class STPCardExpiryInputTextFieldValidator: STPInputTextFieldValidator { diff --git a/Stripe/STPCardNumberInputTextFieldFormatter.swift b/Stripe/STPCardNumberInputTextFieldFormatter.swift index ab4a6619918..c19d2f24118 100644 --- a/Stripe/STPCardNumberInputTextFieldFormatter.swift +++ b/Stripe/STPCardNumberInputTextFieldFormatter.swift @@ -7,6 +7,7 @@ // import UIKit +@_spi(STP) import StripeCore class STPCardNumberInputTextFieldFormatter: STPNumericDigitInputTextFormatter { diff --git a/Stripe/STPFormTextField.swift b/Stripe/STPFormTextField.swift index 1fde7cb0e98..c0a65ff48a3 100644 --- a/Stripe/STPFormTextField.swift +++ b/Stripe/STPFormTextField.swift @@ -7,6 +7,7 @@ // import UIKit +@_spi(STP) import StripeCore enum STPFormTextFieldAutoFormattingBehavior: Int { case none diff --git a/Stripe/STPPaymentContext.swift b/Stripe/STPPaymentContext.swift index 2fcfd83aefe..92e5baf3afb 100644 --- a/Stripe/STPPaymentContext.swift +++ b/Stripe/STPPaymentContext.swift @@ -14,6 +14,7 @@ import PassKit /// An `STPPaymentContext` keeps track of all of the state around a payment. It will manage fetching a user's saved payment methods, tracking any information they select, and prompting them for required additional information before completing their purchase. It can be used to power your application's "payment confirmation" page with just a few lines of code. /// `STPPaymentContext` also provides a unified interface to multiple payment methods - for example, you can write a single integration to accept both credit card payments and Apple Pay. /// `STPPaymentContext` saves information about a user's payment methods to a Stripe customer object, and requires an `STPCustomerContext` to manage retrieving and modifying the customer. +@objc(STPPaymentContext) public class STPPaymentContext: NSObject, STPAuthenticationContext, STPPaymentOptionsViewControllerDelegate, STPShippingAddressViewControllerDelegate { diff --git a/Stripe/STPPaymentHandler.swift b/Stripe/STPPaymentHandler.swift index 13826c91255..9b14f31d221 100644 --- a/Stripe/STPPaymentHandler.swift +++ b/Stripe/STPPaymentHandler.swift @@ -10,6 +10,7 @@ import Foundation import PassKit import SafariServices @_spi(STP) import StripeCore +@_spi(STP) import StripeApplePay #if canImport(Stripe3DS2) import Stripe3DS2 @@ -34,7 +35,7 @@ import SafariServices /// Indicates that the action requires an authentication app, but either the app is not installed or the request to switch to the app was denied. @objc(STPPaymentHandlerRequiredAppNotAvailableErrorCode) case requiredAppNotAvailable - + /// Attach a payment method to the PaymentIntent or SetupIntent before using `STPPaymentHandler`. @objc(STPPaymentHandlerRequiresPaymentMethodErrorCode) case requiresPaymentMethodErrorCode diff --git a/Stripe/STPPostalCodeInputTextFieldFormatter.swift b/Stripe/STPPostalCodeInputTextFieldFormatter.swift index f0c6422a599..0ba53007773 100644 --- a/Stripe/STPPostalCodeInputTextFieldFormatter.swift +++ b/Stripe/STPPostalCodeInputTextFieldFormatter.swift @@ -7,6 +7,7 @@ // import UIKit +@_spi(STP) import StripeCore @_spi(STP) import StripeUICore class STPPostalCodeInputTextFieldFormatter: STPInputTextFieldFormatter { diff --git a/Stripe/STPPostalCodeValidator.swift b/Stripe/STPPostalCodeValidator.swift index e581eb10756..96784893c5a 100644 --- a/Stripe/STPPostalCodeValidator.swift +++ b/Stripe/STPPostalCodeValidator.swift @@ -7,6 +7,7 @@ // import Foundation +@_spi(STP) import StripeCore @_spi(STP) import StripeUICore @objc enum STPPostalCodeIntendedUsage: Int { diff --git a/Stripe/STPPromise.swift b/Stripe/STPPromise.swift index de852cd9a4d..721213490c4 100644 --- a/Stripe/STPPromise.swift +++ b/Stripe/STPPromise.swift @@ -7,6 +7,7 @@ // import Foundation +@_spi(STP) import StripeCore class STPPromise: NSObject { typealias STPPromiseErrorBlock = (Error) -> Void diff --git a/Stripe/StripeApplePay+Import.swift b/Stripe/StripeApplePay+Import.swift new file mode 100644 index 00000000000..58cc846640b --- /dev/null +++ b/Stripe/StripeApplePay+Import.swift @@ -0,0 +1,11 @@ +// +// StripeApplePay+Import.swift +// StripeiOS +// +// Created by David Estes on 11/16/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_exported import StripeApplePay + diff --git a/Stripe/UserDefaults+Stripe.swift b/Stripe/UserDefaults+Stripe.swift index 63e7c50e3a7..8c7c52d6744 100644 --- a/Stripe/UserDefaults+Stripe.swift +++ b/Stripe/UserDefaults+Stripe.swift @@ -13,8 +13,6 @@ extension UserDefaults { enum StripeKeys: String { /// The key for a dictionary of Customer id to their last selected payment method ID case customerToLastSelectedPaymentMethod = "com.stripe.lib:STPStripeCustomerToLastSelectedPaymentMethodKey" - /// The key for a dictionary FraudDetectionData dictionary - case fraudDetectionData = "com.stripe.lib:FraudDetectionDataKey" } var customerToLastSelectedPaymentMethod: [String: String]? { @@ -28,31 +26,5 @@ extension UserDefaults { } } - var fraudDetectionData: FraudDetectionData? { - get { - let key = StripeKeys.fraudDetectionData.rawValue - guard let data = data(forKey: key) else { - return nil - } - do { - return try JSONDecoder().decode(FraudDetectionData.self, from: data) - } - catch(let e) { - assertionFailure("\(e)") - return nil - } - } - set { - let key = StripeKeys.fraudDetectionData.rawValue - do { - let data = try JSONEncoder().encode(newValue) - setValue(data, forKey: key) - } - catch(let e) { - assertionFailure("\(e)") - return - } - } - } } diff --git a/Stripe/_stpobjc_STPApplePayContext.swift b/Stripe/_stpobjc_STPApplePayContext.swift new file mode 100644 index 00000000000..0a863b70ad5 --- /dev/null +++ b/Stripe/_stpobjc_STPApplePayContext.swift @@ -0,0 +1,268 @@ +// +// _stpobjc_STPApplePayContext.swift +// StripeiOS +// +// Created by David Estes on 11/16/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +// This is a workaround for the lack of cross-Swift-module extension support in +// the iOS 11 and iOS 12 Objective-C runtime. + +import Foundation +import PassKit +import UIKit +@_spi(STP) import StripeCore +@_spi(STP) import StripeApplePay + +/* + NOTE: Because '@objc' is not supported in cross-module extensions below iOS 13, a separate + Objective-C compatible wrapper of `STPApplePayContext` is needed. When updating + documentation comments, make sure to update the corresponding comments in + `STPApplePayContext` as well. + */ + +/// A helper class used to bridge StripeApplePay.framework with the legacy Stripe.framework objects. +@objc(STPApplePayContextLegacyHelper) +class STPApplePayContextLegacyHelper: NSObject { + @objc class func performDidCreatePaymentMethod(_ storage: _stpinternal_ApplePayContextDidCreatePaymentMethodStorage) { + let delegate = storage.delegate as! STPApplePayContextDelegate + // Convert the PaymentMethod to an STPPaymentMethod: + guard let stpPaymentMethod = STPPaymentMethod.decodedObject(fromAPIResponse: storage.paymentMethod.allResponseFields) else { + assertionFailure("Failed to convert PaymentMethod to STPPaymentMethod") + return + } + delegate.applePayContext(storage.context, didCreatePaymentMethod: stpPaymentMethod, paymentInformation: storage.paymentInformation, completion: storage.completion) + } + + @objc class func performDidComplete(_ storage: _stpinternal_ApplePayContextDidCompleteStorage) { + let delegate = storage.delegate as! STPApplePayContextDelegate + let stpStatus = STPPaymentStatus(applePayStatus: storage.status) + + // If this is a modern API error, convert it down to a legacy STPError. + // This is to avoid changing the API experience for users. + // We can re-evaluate this as we release more of the modern API. + if let modernError = storage.error as? StripeError { + storage.error = NSError.stp_error(from: modernError) + } + + delegate.applePayContext(storage.context, didCompleteWith: stpStatus, error: storage.error) + } + +} + +/// Implement the required methods of this delegate to supply a PaymentIntent to STPApplePayContext and be notified of the completion of the Apple Pay payment. +/// You may also implement the optional delegate methods to handle shipping methods and shipping address changes e.g. to verify you can ship to the address, or update the payment amount. +@objc(_stpinternal_apContextDelegate) +public protocol STPApplePayContextDelegate: _stpinternal_STPApplePayContextDelegateBase { + /// Called after the customer has authorized Apple Pay. Implement this method to call the completion block with the client secret of a PaymentIntent or SetupIntent. + /// - Parameters: + /// - paymentMethod: The PaymentMethod that represents the customer's Apple Pay payment method. + /// If you create the PaymentIntent with confirmation_method=manual, pass `paymentMethod.stripeId` as the payment_method and confirm=true. Otherwise, you can ignore this parameter. + /// - paymentInformation: The underlying PKPayment created by Apple Pay. + /// If you create the PaymentIntent with confirmation_method=manual, you can collect shipping information using its `shippingContact` and `shippingMethod` properties. + /// - completion: Call this with the PaymentIntent or SetupIntent client secret, or the error that occurred creating the PaymentIntent or SetupIntent. + @objc(applePayContext:didCreatePaymentMethod:paymentInformation:completion:) + func applePayContext( + _ context: STPApplePayContext, + didCreatePaymentMethod paymentMethod: STPPaymentMethod, + paymentInformation: PKPayment, + completion: @escaping STPIntentClientSecretCompletionBlock + ) + + /// Called after the Apple Pay sheet is dismissed with the result of the payment. + /// Your implementation could stop a spinner and display a receipt view or error to the customer, for example. + /// - Parameters: + /// - status: The status of the payment + /// - error: The error that occurred, if any. + @objc(applePayContext:didCompleteWithStatus:error:) + func applePayContext( + _ context: STPApplePayContext, + didCompleteWith status: STPPaymentStatus, + error: Error? + ) +} + +/// Implement the required methods of this delegate to supply a PaymentIntent to STPApplePayContext and be notified of the completion of the Apple Pay payment. +/// You may also implement the optional delegate methods to handle shipping methods and shipping address changes e.g. to verify you can ship to the address, or update the payment amount. +/// :nodoc: +@objc(STPApplePayContextDelegate) public protocol _stpobjc_APContextDelegate: NSObjectProtocol { + /// Called after the customer has authorized Apple Pay. Implement this method to call the completion block with the client secret of a PaymentIntent or SetupIntent. + /// - Parameters: + /// - paymentMethod: The PaymentMethod that represents the customer's Apple Pay payment method. + /// If you create the PaymentIntent with confirmation_method=manual, pass `paymentMethod.stripeId` as the payment_method and confirm=true. Otherwise, you can ignore this parameter. + /// - paymentInformation: The underlying PKPayment created by Apple Pay. + /// If you create the PaymentIntent with confirmation_method=manual, you can collect shipping information using its `shippingContact` and `shippingMethod` properties. + /// - completion: Call this with the PaymentIntent or SetupIntent client secret, or the error that occurred creating the PaymentIntent or SetupIntent. + @objc(applePayContext:didCreatePaymentMethod:paymentInformation:completion:) + func applePayContext( + _ context: _stpobjc_APContext, + didCreatePaymentMethod paymentMethod: STPPaymentMethod, + paymentInformation: PKPayment, + completion: @escaping STPIntentClientSecretCompletionBlock + ) + + /// Called after the Apple Pay sheet is dismissed with the result of the payment. + /// Your implementation could stop a spinner and display a receipt view or error to the customer, for example. + /// - Parameters: + /// - status: The status of the payment + /// - error: The error that occurred, if any. + @objc(applePayContext:didCompleteWithStatus:error:) + func applePayContext( + _ context: _stpobjc_APContext, + didCompleteWith status: STPPaymentStatus, + error: Error? + ) + + /// Called when the user selects a new shipping method. The delegate should determine + /// shipping costs based on the shipping method and either the shipping address supplied in the original + /// PKPaymentRequest or the address fragment provided by the last call to paymentAuthorizationController: + /// didSelectShippingContact:completion:. + /// You must invoke the completion block with an updated array of PKPaymentSummaryItem objects. + @objc(applePayContext:didSelectShippingMethod:handler:) + optional func applePayContext( + _ context: _stpobjc_APContext, + didSelect shippingMethod: PKShippingMethod, + handler: @escaping (_ update: PKPaymentRequestShippingMethodUpdate) -> Void + ) + + /// Called when the user has selected a new shipping address. You should inspect the + /// address and must invoke the completion block with an updated array of PKPaymentSummaryItem objects. + /// @note To maintain privacy, the shipping information is anonymized. For example, in the United States it only includes the city, state, and zip code. This provides enough information to calculate shipping costs, without revealing sensitive information until the user actually approves the purchase. + /// Receive full shipping information in the paymentInformation passed to `applePayContext:didCreatePaymentMethod:paymentInformation:completion:` + @objc optional func applePayContext( + _ context: _stpobjc_APContext, + didSelectShippingContact contact: PKContact, + handler: @escaping (_ update: PKPaymentRequestShippingContactUpdate) -> Void + ) +} + + +class STPApplePayContextBridgeDelegate: NSObject, STPApplePayContextDelegate { + func applePayContext(_ context: STPApplePayContext, didCreatePaymentMethod paymentMethod: STPPaymentMethod, paymentInformation: PKPayment, completion: @escaping STPIntentClientSecretCompletionBlock) { + objcDelegate?.applePayContext(.init(applePayContext: context), didCreatePaymentMethod: paymentMethod, paymentInformation: paymentInformation, completion: completion) + } + + func applePayContext(_ context: STPApplePayContext, didCompleteWith status: STPPaymentStatus, error: Error?) { + objcDelegate?.applePayContext(.init(applePayContext: context), didCompleteWith: status, error: error) + } + + func applePayContext(_ context: STPApplePayContext, didSelect shippingMethod: PKShippingMethod, handler: @escaping (PKPaymentRequestShippingMethodUpdate) -> Void) { + objcDelegate?.applePayContext?(.init(applePayContext: context), didSelect: shippingMethod, handler: handler) + } + + func applePayContext(_ context: STPApplePayContext, didSelectShippingContact contact: PKContact, handler: @escaping (PKPaymentRequestShippingContactUpdate) -> Void) { + objcDelegate?.applePayContext?(.init(applePayContext: context), didSelectShippingContact: contact, handler: handler) + } + + /// :nodoc: + public override func responds(to aSelector: Selector!) -> Bool { + // Pass through responds(to:) so that we only respond to the shipping/contact + // messages if the underlying delegate also does so. + return objcDelegate?.responds(to: aSelector) ?? false + } + + weak var objcDelegate: _stpobjc_APContextDelegate? + + init?(delegate: _stpobjc_APContextDelegate?) { + if let delegate = delegate { + self.objcDelegate = delegate + } else { + return nil + } + } + +} + +/// An Objective-C bridge for STPApplePayContext. +/// :nodoc: +@objc(STPApplePayContext) +public class _stpobjc_APContext: NSObject { + @objc var _applePayContext: STPApplePayContext + + /// Initializes this class. + /// @note This may return nil if the request is invalid e.g. the user is restricted by parental controls, or can't make payments on any of the request's supported networks + /// - Parameters: + /// - paymentRequest: The payment request to use with Apple Pay. + /// - delegate: The delegate. + @objc(initWithPaymentRequest:delegate:) + public required init?(paymentRequest: PKPaymentRequest, delegate: _stpobjc_APContextDelegate?) { + bridgeDelegate = STPApplePayContextBridgeDelegate(delegate: delegate) + guard let context = STPApplePayContext(paymentRequest: paymentRequest, delegate: bridgeDelegate) else { + return nil + } + STPAnalyticsClient.sharedClient.addClass(toProductUsageIfNecessary: Self.self) + _applePayContext = context + super.init() + context.applePayContextObjCBridge = self + } + + // We want to maintain a strong reference to this bridge delegate, + // which contains a weak reference to the underlying Objective-C delegate. + var bridgeDelegate: STPApplePayContextBridgeDelegate? + + internal init(applePayContext: STPApplePayContext) { + _applePayContext = applePayContext + } + + /// Presents the Apple Pay sheet from the specified view controller, starting the payment process. + /// @note This method should only be called once; create a new instance of STPApplePayContext every time you present Apple Pay. + /// @deprecated A presenting UIViewController is no longer needed. Use presentApplePay(completion:) instead. + /// - Parameters: + /// - viewController: The UIViewController instance to present the Apple Pay sheet on + /// - completion: Called after the Apple Pay sheet is presented + @objc(presentApplePayOnViewController:completion:) + @available( + *, deprecated, message: "Use `presentApplePay(completion:)` instead.", + renamed: "presentApplePay(completion:)" + ) + public func presentApplePay( + on viewController: UIViewController, completion: STPVoidBlock? = nil + ) { + _applePayContext.presentApplePay(on: viewController, completion: completion) + } + + /// Presents the Apple Pay sheet from the key window, starting the payment process. + /// @note This method should only be called once; create a new instance of STPApplePayContext every time you present Apple Pay. + /// - Parameters: + /// - completion: Called after the Apple Pay sheet is presented + @objc(presentApplePayWithCompletion:) + @available( + iOSApplicationExtension, unavailable, + message: "Use `presentApplePay(from:completion:)` in App Extensions." + ) + @available( + macCatalystApplicationExtension, unavailable, + message: "Use `presentApplePay(from:completion:)` in App Extensions." + ) + public func presentApplePay(completion: STPVoidBlock? = nil) { + _applePayContext.presentApplePay(completion: completion) + } + + /// Presents the Apple Pay sheet from the specified window, starting the payment process. + /// @note This method should only be called once; create a new instance of STPApplePayContext every time you present Apple Pay. + /// - Parameters: + /// - window: The UIWindow to host the Apple Pay sheet + /// - completion: Called after the Apple Pay sheet is presented + @objc(presentApplePayFromWindow:withCompletion:) + public func presentApplePay(from window: UIWindow?, completion: STPVoidBlock? = nil) { + _applePayContext.presentApplePay(from: window, completion: completion) + } + + /// The STPAPIClient instance to use to make API requests to Stripe. + /// Defaults to `STPAPIClient.shared`. + @available(swift, deprecated: 0.0.1, renamed: "apiClient") + @objc(apiClient) public var _objc_apiClient: _stpobjc_STPAPIClient { + get { + _stpobjc_STPAPIClient(apiClient: _applePayContext.apiClient) + } + set { + _applePayContext.apiClient = newValue._apiClient + } + } +} + +/// :nodoc: +@_spi(STP) extension _stpobjc_APContext: STPAnalyticsProtocol { + @_spi(STP) public static var stp_analyticsIdentifier = "objc_STPApplePayContext" +} diff --git a/StripeApplePay.podspec b/StripeApplePay.podspec new file mode 100644 index 00000000000..25a5fad4781 --- /dev/null +++ b/StripeApplePay.podspec @@ -0,0 +1,21 @@ +Pod::Spec.new do |s| + s.name = 'StripeApplePay' + + # Do not update s.version directly. + # Instead, update the VERSION file and run ./ci_scripts/update_version.sh + s.version = '21.11.1' + + s.summary = 'StripeApplePay is a lightweight Apple Pay SDK intended for building App Clips '\ + 'or other size-constrained apps.' + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.homepage = 'https://stripe.com/docs/apple-pay' + s.authors = { 'Stripe' => 'support+github@stripe.com' } + s.source = { :git => 'https://github.com/stripe/stripe-ios.git', :tag => "#{s.version}" } + s.frameworks = 'Foundation', 'Security', 'WebKit', 'PassKit', 'Contacts', 'CoreLocation' + s.requires_arc = true + s.platform = :ios + s.ios.deployment_target = '11.0' + s.swift_version = '5.0' + s.source_files = 'StripeApplePay/StripeApplePay/**/*.swift' + s.dependency 'StripeCore', "#{s.version}" +end diff --git a/StripeApplePay/StripeApplePay.xcodeproj/project.pbxproj b/StripeApplePay/StripeApplePay.xcodeproj/project.pbxproj index de7ae67f2ec..9f8fea4fdb6 100644 --- a/StripeApplePay/StripeApplePay.xcodeproj/project.pbxproj +++ b/StripeApplePay/StripeApplePay.xcodeproj/project.pbxproj @@ -7,8 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 312656FF27AC9BBF00D2F8A8 /* STPAnalyticsClient+ApplePayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312656FE27AC9BBF00D2F8A8 /* STPAnalyticsClient+ApplePayTest.swift */; }; 3137B6AD2743816300CE7F5C /* StripeCore+Import.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3137B6AC2743816300CE7F5C /* StripeCore+Import.swift */; }; 3137B6AF2743837400CE7F5C /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B6AE2743837400CE7F5C /* StripeCore.framework */; }; + 315BDBE12788E9D8007BD11F /* STPAPIClient+ApplePay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315BDBE02788E9D8007BD11F /* STPAPIClient+ApplePay.swift */; }; + 3163E4A22787BB8200956D86 /* STPApplePayContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3163E4A12787BB8200956D86 /* STPApplePayContext.swift */; }; + 3163E4A42787BD0E00956D86 /* Blocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3163E4A32787BD0E00956D86 /* Blocks.swift */; }; + 31689984279B34BA00FEBDE3 /* STPAnalyticsClient+PaymentsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31689983279B34BA00FEBDE3 /* STPAnalyticsClient+PaymentsAPI.swift */; }; 3181D736273A00BF00525C02 /* StripeApplePay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3181D72D273A00BE00525C02 /* StripeApplePay.framework */; }; 3181D73B273A00BF00525C02 /* StripeApplePayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3181D73A273A00BF00525C02 /* StripeApplePayTests.swift */; }; 3181D73C273A00BF00525C02 /* StripeApplePay.h in Headers */ = {isa = PBXBuildFile; fileRef = 3181D730273A00BE00525C02 /* StripeApplePay.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -22,10 +27,34 @@ 3181D757273A011B00525C02 /* StripeiOS-Shared.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 3181D74D273A011B00525C02 /* StripeiOS-Shared.xcconfig */; }; 3181D758273A011B00525C02 /* Project-Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 3181D74E273A011B00525C02 /* Project-Debug.xcconfig */; }; 3181D759273A011B00525C02 /* StripeiOS-Release.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 3181D74F273A011B00525C02 /* StripeiOS-Release.xcconfig */; }; - 3181D75D273A03DF00525C02 /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3181D75C273A03DF00525C02 /* Placeholder.swift */; }; 319E82F2274318DE00F202D6 /* StripeApplePayTestUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 319E82F1274318DE00F202D6 /* StripeApplePayTestUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; 319E82F72743190B00F202D6 /* StripeApplePay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3181D72D273A00BE00525C02 /* StripeApplePay.framework */; }; 319E82F82743191200F202D6 /* StripeApplePayTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 319E82EF274318DE00F202D6 /* StripeApplePayTestUtils.framework */; }; + 31ACFB2727A0B19000988C8E /* STPApplePayContext+LegacySupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ACFB2627A0B19000988C8E /* STPApplePayContext+LegacySupport.swift */; }; + 31AF4409279F725500BCEEF4 /* STPAnalyticsClient+Payments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AF4408279F725500BCEEF4 /* STPAnalyticsClient+Payments.swift */; }; + 31AF440B27A0887400BCEEF4 /* STPAPIClient+PaymentsCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AF440A27A0887400BCEEF4 /* STPAPIClient+PaymentsCore.swift */; }; + 31E6D9452744404700A89B6D /* PaymentMethodParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D93C2744404700A89B6D /* PaymentMethodParams.swift */; }; + 31E6D9462744404700A89B6D /* Address.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D93D2744404700A89B6D /* Address.swift */; }; + 31E6D9472744404700A89B6D /* SetupIntentParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D93E2744404700A89B6D /* SetupIntentParams.swift */; }; + 31E6D9482744404700A89B6D /* PaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D93F2744404700A89B6D /* PaymentMethod.swift */; }; + 31E6D9492744404700A89B6D /* SetupIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D9402744404700A89B6D /* SetupIntent.swift */; }; + 31E6D94A2744404700A89B6D /* PaymentIntentParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D9412744404700A89B6D /* PaymentIntentParams.swift */; }; + 31E6D94B2744404700A89B6D /* BillingDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D9422744404700A89B6D /* BillingDetails.swift */; }; + 31E6D94C2744404700A89B6D /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D9432744404700A89B6D /* Token.swift */; }; + 31E6D94D2744404700A89B6D /* PaymentIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D9442744404700A89B6D /* PaymentIntent.swift */; }; + 31E6D9522744405000A89B6D /* SetupIntent+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D94E2744405000A89B6D /* SetupIntent+API.swift */; }; + 31E6D9532744405000A89B6D /* PaymentIntent+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D94F2744405000A89B6D /* PaymentIntent+API.swift */; }; + 31E6D9542744405000A89B6D /* PaymentMethod+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D9502744405000A89B6D /* PaymentMethod+API.swift */; }; + 31E6D9552744405000A89B6D /* Token+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D9512744405000A89B6D /* Token+API.swift */; }; + 31E6D95C274440B900A89B6D /* STPPaymentMethodFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D95B274440B900A89B6D /* STPPaymentMethodFunctionalTest.swift */; }; + 31E6D95E2744444A00A89B6D /* PKPayment+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D95D2744444A00A89B6D /* PKPayment+Stripe.swift */; }; + 31E6D960274444AB00A89B6D /* BillingDetails+ApplePay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D95F274444AB00A89B6D /* BillingDetails+ApplePay.swift */; }; + 31E6D9662744467D00A89B6D /* PKContact+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D9652744467D00A89B6D /* PKContact+Stripe.swift */; }; + 31E6D96A274456D100A89B6D /* STPTelemetryClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D969274456D100A89B6D /* STPTelemetryClient.swift */; }; + 31E6D96C274456E000A89B6D /* FraudDetectionData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D96B274456E000A89B6D /* FraudDetectionData.swift */; }; + 31E6D96F2744572F00A89B6D /* UserDefaults+PaymentsCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D96E2744572F00A89B6D /* UserDefaults+PaymentsCore.swift */; }; + 31E6D9722744591800A89B6D /* STPTelemetryClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D9702744591700A89B6D /* STPTelemetryClientTest.swift */; }; + 31E6D9732744591800A89B6D /* STPTelemetryClientFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D9712744591700A89B6D /* STPTelemetryClientFunctionalTest.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -39,9 +68,14 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 312656FE27AC9BBF00D2F8A8 /* STPAnalyticsClient+ApplePayTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAnalyticsClient+ApplePayTest.swift"; sourceTree = ""; }; 3137B690274330E200CE7F5C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3137B6AC2743816300CE7F5C /* StripeCore+Import.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StripeCore+Import.swift"; sourceTree = ""; }; 3137B6AE2743837400CE7F5C /* StripeCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = StripeCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 315BDBE02788E9D8007BD11F /* STPAPIClient+ApplePay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+ApplePay.swift"; sourceTree = ""; }; + 3163E4A12787BB8200956D86 /* STPApplePayContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPApplePayContext.swift; sourceTree = ""; }; + 3163E4A32787BD0E00956D86 /* Blocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Blocks.swift; path = ../Blocks.swift; sourceTree = ""; }; + 31689983279B34BA00FEBDE3 /* STPAnalyticsClient+PaymentsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAnalyticsClient+PaymentsAPI.swift"; sourceTree = ""; }; 3181D72D273A00BE00525C02 /* StripeApplePay.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeApplePay.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3181D730273A00BE00525C02 /* StripeApplePay.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StripeApplePay.h; sourceTree = ""; }; 3181D735273A00BF00525C02 /* StripeApplePayTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StripeApplePayTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -57,9 +91,33 @@ 3181D74E273A011B00525C02 /* Project-Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "Project-Debug.xcconfig"; sourceTree = ""; }; 3181D74F273A011B00525C02 /* StripeiOS-Release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "StripeiOS-Release.xcconfig"; sourceTree = ""; }; 3181D75A273A020F00525C02 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3181D75C273A03DF00525C02 /* Placeholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Placeholder.swift; sourceTree = ""; }; 319E82EF274318DE00F202D6 /* StripeApplePayTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeApplePayTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 319E82F1274318DE00F202D6 /* StripeApplePayTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StripeApplePayTestUtils.h; sourceTree = ""; }; + 31ACFB2627A0B19000988C8E /* STPApplePayContext+LegacySupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPApplePayContext+LegacySupport.swift"; sourceTree = ""; }; + 31AF4408279F725500BCEEF4 /* STPAnalyticsClient+Payments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAnalyticsClient+Payments.swift"; sourceTree = ""; }; + 31AF440A27A0887400BCEEF4 /* STPAPIClient+PaymentsCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+PaymentsCore.swift"; sourceTree = ""; }; + 31E6D93C2744404700A89B6D /* PaymentMethodParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentMethodParams.swift; sourceTree = ""; }; + 31E6D93D2744404700A89B6D /* Address.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Address.swift; sourceTree = ""; }; + 31E6D93E2744404700A89B6D /* SetupIntentParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupIntentParams.swift; sourceTree = ""; }; + 31E6D93F2744404700A89B6D /* PaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentMethod.swift; sourceTree = ""; }; + 31E6D9402744404700A89B6D /* SetupIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupIntent.swift; sourceTree = ""; }; + 31E6D9412744404700A89B6D /* PaymentIntentParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentIntentParams.swift; sourceTree = ""; }; + 31E6D9422744404700A89B6D /* BillingDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BillingDetails.swift; sourceTree = ""; }; + 31E6D9432744404700A89B6D /* Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; + 31E6D9442744404700A89B6D /* PaymentIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentIntent.swift; sourceTree = ""; }; + 31E6D94E2744405000A89B6D /* SetupIntent+API.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SetupIntent+API.swift"; sourceTree = ""; }; + 31E6D94F2744405000A89B6D /* PaymentIntent+API.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PaymentIntent+API.swift"; sourceTree = ""; }; + 31E6D9502744405000A89B6D /* PaymentMethod+API.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PaymentMethod+API.swift"; sourceTree = ""; }; + 31E6D9512744405000A89B6D /* Token+API.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Token+API.swift"; sourceTree = ""; }; + 31E6D95B274440B900A89B6D /* STPPaymentMethodFunctionalTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodFunctionalTest.swift; sourceTree = ""; }; + 31E6D95D2744444A00A89B6D /* PKPayment+Stripe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PKPayment+Stripe.swift"; sourceTree = ""; }; + 31E6D95F274444AB00A89B6D /* BillingDetails+ApplePay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BillingDetails+ApplePay.swift"; sourceTree = ""; }; + 31E6D9652744467D00A89B6D /* PKContact+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PKContact+Stripe.swift"; sourceTree = ""; }; + 31E6D969274456D100A89B6D /* STPTelemetryClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPTelemetryClient.swift; sourceTree = ""; }; + 31E6D96B274456E000A89B6D /* FraudDetectionData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FraudDetectionData.swift; sourceTree = ""; }; + 31E6D96E2744572F00A89B6D /* UserDefaults+PaymentsCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+PaymentsCore.swift"; sourceTree = ""; }; + 31E6D9702744591700A89B6D /* STPTelemetryClientTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPTelemetryClientTest.swift; sourceTree = ""; }; + 31E6D9712744591700A89B6D /* STPTelemetryClientFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPTelemetryClientFunctionalTest.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -91,6 +149,16 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 3163E4A02787BB7900956D86 /* ApplePayContext */ = { + isa = PBXGroup; + children = ( + 315BDBE02788E9D8007BD11F /* STPAPIClient+ApplePay.swift */, + 3163E4A12787BB8200956D86 /* STPApplePayContext.swift */, + 31ACFB2627A0B19000988C8E /* STPApplePayContext+LegacySupport.swift */, + ); + path = ApplePayContext; + sourceTree = ""; + }; 3181D723273A00BE00525C02 = { isa = PBXGroup; children = ( @@ -126,6 +194,9 @@ 3181D739273A00BF00525C02 /* StripeApplePayTests */ = { isa = PBXGroup; children = ( + 31E6D9742744591A00A89B6D /* PaymentsCore */, + 31E6D95B274440B900A89B6D /* STPPaymentMethodFunctionalTest.swift */, + 312656FE27AC9BBF00D2F8A8 /* STPAnalyticsClient+ApplePayTest.swift */, 3181D73A273A00BF00525C02 /* StripeApplePayTests.swift */, ); path = StripeApplePayTests; @@ -152,7 +223,9 @@ 3181D75B273A03D300525C02 /* Source */ = { isa = PBXGroup; children = ( - 3181D75C273A03DF00525C02 /* Placeholder.swift */, + 3163E4A02787BB7900956D86 /* ApplePayContext */, + 31E6D967274456A100A89B6D /* PaymentsCore */, + 31E6D9562744406300A89B6D /* Extensions */, 3137B6AC2743816300CE7F5C /* StripeCore+Import.swift */, ); path = Source; @@ -175,6 +248,92 @@ name = Frameworks; sourceTree = ""; }; + 31AF4407279F71E400BCEEF4 /* Analytics */ = { + isa = PBXGroup; + children = ( + 31689983279B34BA00FEBDE3 /* STPAnalyticsClient+PaymentsAPI.swift */, + 31AF4408279F725500BCEEF4 /* STPAnalyticsClient+Payments.swift */, + ); + path = Analytics; + sourceTree = ""; + }; + 31E6D93A27443EE300A89B6D /* API */ = { + isa = PBXGroup; + children = ( + 31E6D94F2744405000A89B6D /* PaymentIntent+API.swift */, + 31E6D9502744405000A89B6D /* PaymentMethod+API.swift */, + 31E6D94E2744405000A89B6D /* SetupIntent+API.swift */, + 31E6D9512744405000A89B6D /* Token+API.swift */, + 31E6D93B2744404700A89B6D /* Models */, + ); + path = API; + sourceTree = ""; + }; + 31E6D93B2744404700A89B6D /* Models */ = { + isa = PBXGroup; + children = ( + 31E6D93C2744404700A89B6D /* PaymentMethodParams.swift */, + 31E6D93D2744404700A89B6D /* Address.swift */, + 31E6D93E2744404700A89B6D /* SetupIntentParams.swift */, + 31E6D93F2744404700A89B6D /* PaymentMethod.swift */, + 31E6D9402744404700A89B6D /* SetupIntent.swift */, + 31E6D9412744404700A89B6D /* PaymentIntentParams.swift */, + 31E6D9422744404700A89B6D /* BillingDetails.swift */, + 31E6D9432744404700A89B6D /* Token.swift */, + 31E6D9442744404700A89B6D /* PaymentIntent.swift */, + ); + path = Models; + sourceTree = ""; + }; + 31E6D9562744406300A89B6D /* Extensions */ = { + isa = PBXGroup; + children = ( + 31E6D95F274444AB00A89B6D /* BillingDetails+ApplePay.swift */, + 31E6D95D2744444A00A89B6D /* PKPayment+Stripe.swift */, + 31E6D9652744467D00A89B6D /* PKContact+Stripe.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 31E6D967274456A100A89B6D /* PaymentsCore */ = { + isa = PBXGroup; + children = ( + 31AF4407279F71E400BCEEF4 /* Analytics */, + 3163E4A32787BD0E00956D86 /* Blocks.swift */, + 31E6D96D2744572400A89B6D /* Categories */, + 31E6D968274456BC00A89B6D /* Telemetry */, + 31E6D93A27443EE300A89B6D /* API */, + ); + path = PaymentsCore; + sourceTree = ""; + }; + 31E6D968274456BC00A89B6D /* Telemetry */ = { + isa = PBXGroup; + children = ( + 31E6D96B274456E000A89B6D /* FraudDetectionData.swift */, + 31E6D969274456D100A89B6D /* STPTelemetryClient.swift */, + ); + path = Telemetry; + sourceTree = ""; + }; + 31E6D96D2744572400A89B6D /* Categories */ = { + isa = PBXGroup; + children = ( + 31E6D96E2744572F00A89B6D /* UserDefaults+PaymentsCore.swift */, + 31AF440A27A0887400BCEEF4 /* STPAPIClient+PaymentsCore.swift */, + ); + path = Categories; + sourceTree = ""; + }; + 31E6D9742744591A00A89B6D /* PaymentsCore */ = { + isa = PBXGroup; + children = ( + 31E6D9712744591700A89B6D /* STPTelemetryClientFunctionalTest.swift */, + 31E6D9702744591700A89B6D /* STPTelemetryClientTest.swift */, + ); + path = PaymentsCore; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -332,8 +491,33 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 31E6D96C274456E000A89B6D /* FraudDetectionData.swift in Sources */, + 315BDBE12788E9D8007BD11F /* STPAPIClient+ApplePay.swift in Sources */, + 31E6D9522744405000A89B6D /* SetupIntent+API.swift in Sources */, + 31E6D94A2744404700A89B6D /* PaymentIntentParams.swift in Sources */, + 31E6D9542744405000A89B6D /* PaymentMethod+API.swift in Sources */, + 31E6D94C2744404700A89B6D /* Token.swift in Sources */, + 31ACFB2727A0B19000988C8E /* STPApplePayContext+LegacySupport.swift in Sources */, 3137B6AD2743816300CE7F5C /* StripeCore+Import.swift in Sources */, - 3181D75D273A03DF00525C02 /* Placeholder.swift in Sources */, + 31E6D9472744404700A89B6D /* SetupIntentParams.swift in Sources */, + 31AF4409279F725500BCEEF4 /* STPAnalyticsClient+Payments.swift in Sources */, + 31689984279B34BA00FEBDE3 /* STPAnalyticsClient+PaymentsAPI.swift in Sources */, + 31E6D960274444AB00A89B6D /* BillingDetails+ApplePay.swift in Sources */, + 31E6D94D2744404700A89B6D /* PaymentIntent.swift in Sources */, + 31E6D96A274456D100A89B6D /* STPTelemetryClient.swift in Sources */, + 31E6D9532744405000A89B6D /* PaymentIntent+API.swift in Sources */, + 31E6D95E2744444A00A89B6D /* PKPayment+Stripe.swift in Sources */, + 31E6D9452744404700A89B6D /* PaymentMethodParams.swift in Sources */, + 31E6D94B2744404700A89B6D /* BillingDetails.swift in Sources */, + 3163E4A22787BB8200956D86 /* STPApplePayContext.swift in Sources */, + 31E6D96F2744572F00A89B6D /* UserDefaults+PaymentsCore.swift in Sources */, + 31E6D9552744405000A89B6D /* Token+API.swift in Sources */, + 3163E4A42787BD0E00956D86 /* Blocks.swift in Sources */, + 31AF440B27A0887400BCEEF4 /* STPAPIClient+PaymentsCore.swift in Sources */, + 31E6D9492744404700A89B6D /* SetupIntent.swift in Sources */, + 31E6D9662744467D00A89B6D /* PKContact+Stripe.swift in Sources */, + 31E6D9482744404700A89B6D /* PaymentMethod.swift in Sources */, + 31E6D9462744404700A89B6D /* Address.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -341,6 +525,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 312656FF27AC9BBF00D2F8A8 /* STPAnalyticsClient+ApplePayTest.swift in Sources */, + 31E6D9722744591800A89B6D /* STPTelemetryClientTest.swift in Sources */, + 31E6D9732744591800A89B6D /* STPTelemetryClientFunctionalTest.swift in Sources */, + 31E6D95C274440B900A89B6D /* STPPaymentMethodFunctionalTest.swift in Sources */, 3181D73B273A00BF00525C02 /* StripeApplePayTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/StripeApplePay/StripeApplePay.xcodeproj/xcshareddata/xcschemes/StripeApplePay.xcscheme b/StripeApplePay/StripeApplePay.xcodeproj/xcshareddata/xcschemes/StripeApplePay.xcscheme new file mode 100644 index 00000000000..a3b78dbcf89 --- /dev/null +++ b/StripeApplePay/StripeApplePay.xcodeproj/xcshareddata/xcschemes/StripeApplePay.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPAPIClient+ApplePay.swift b/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPAPIClient+ApplePay.swift new file mode 100644 index 00000000000..3076fd6143e --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPAPIClient+ApplePay.swift @@ -0,0 +1,39 @@ +// +// STPAPIClient+ApplePay.swift +// Stripe +// +// Created by Jack Flintermann on 12/19/14. +// + +import Foundation +import PassKit +@_spi(STP) import StripeCore + +/// STPAPIClient extensions to create Stripe Tokens, Sources, or PaymentMethods from Apple Pay PKPayment objects. +extension STPAPIClient { + /// Converts Stripe errors into the appropriate Apple Pay error, for use in `PKPaymentAuthorizationResult`. + /// If the error can be fixed by the customer within the Apple Pay sheet, we return an NSError that can be displayed in the Apple Pay sheet. + /// Otherwise, the original error is returned, resulting in the Apple Pay sheet being dismissed. You should display the error message to the customer afterwards. + /// Currently, we convert billing address related errors into a PKPaymentError that helpfully points to the billing address field in the Apple Pay sheet. + /// Note that Apple Pay should prevent most card errors (e.g. invalid CVC, expired cards) when you add a card to the wallet. + /// - Parameter stripeError: An error from the Stripe SDK. + public class func pkPaymentError(forStripeError stripeError: Error?) -> Error? { + guard let stripeError = stripeError else { + return nil + } + + if (stripeError as NSError).domain == STPError.stripeDomain + && ((stripeError as NSError).userInfo[STPError.cardErrorCodeKey] as? String + == STPCardErrorCode.incorrectZip.rawValue) + { + var userInfo = (stripeError as NSError).userInfo + var errorCode: PKPaymentError.Code = .unknownError + errorCode = .billingContactInvalidError + userInfo[PKPaymentErrorKey.postalAddressUserInfoKey.rawValue] = + CNPostalAddressPostalCodeKey + return NSError( + domain: STPError.stripeDomain, code: errorCode.rawValue, userInfo: userInfo) + } + return stripeError + } +} diff --git a/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPApplePayContext+LegacySupport.swift b/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPApplePayContext+LegacySupport.swift new file mode 100644 index 00000000000..bd4e5f502be --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPApplePayContext+LegacySupport.swift @@ -0,0 +1,50 @@ +// +// STPApplePayContext+LegacySupport.swift +// StripeApplePay +// +// Created by David Estes on 1/25/22. +// + +import Foundation +import PassKit + +/// Internal Apple Pay class. Do not use. +/// :nodoc: +@objc @_spi(STP) public class _stpinternal_ApplePayContextDidCreatePaymentMethodStorage: NSObject { + @_spi(STP) public weak var delegate: _stpinternal_STPApplePayContextDelegateBase? + @_spi(STP) public var context: STPApplePayContext + @_spi(STP) public var paymentMethod: StripeAPI.PaymentMethod + @_spi(STP) public var paymentInformation: PKPayment + @_spi(STP) public var completion: STPIntentClientSecretCompletionBlock + + @_spi(STP) public init(delegate: _stpinternal_STPApplePayContextDelegateBase, + context: STPApplePayContext, + paymentMethod: StripeAPI.PaymentMethod, + paymentInformation: PKPayment, + completion: @escaping STPIntentClientSecretCompletionBlock) { + self.delegate = delegate + self.context = context + self.paymentMethod = paymentMethod + self.paymentInformation = paymentInformation + self.completion = completion + } +} + +/// Internal Apple Pay class. Do not use. +/// :nodoc: +@objc @_spi(STP) public class _stpinternal_ApplePayContextDidCompleteStorage: NSObject { + @_spi(STP) public weak var delegate: _stpinternal_STPApplePayContextDelegateBase? + @_spi(STP) public var context: STPApplePayContext + @_spi(STP) public var status: STPApplePayContext.PaymentStatus + @_spi(STP) public var error: Error? + + @_spi(STP) public init(delegate: _stpinternal_STPApplePayContextDelegateBase, + context: STPApplePayContext, + status: STPApplePayContext.PaymentStatus, + error: Error?) { + self.delegate = delegate + self.context = context + self.status = status + self.error = error + } +} diff --git a/Stripe/STPApplePayContext.swift b/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPApplePayContext.swift similarity index 53% rename from Stripe/STPApplePayContext.swift rename to StripeApplePay/StripeApplePay/Source/ApplePayContext/STPApplePayContext.swift index a022c6c3d7a..f954e186bb7 100644 --- a/Stripe/STPApplePayContext.swift +++ b/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPApplePayContext.swift @@ -11,21 +11,44 @@ import ObjectiveC import PassKit @_spi(STP) import StripeCore -/// Implement the required methods of this delegate to supply a PaymentIntent to STPApplePayContext and be notified of the completion of the Apple Pay payment. +/// :nodoc: +@objc public protocol _stpinternal_STPApplePayContextDelegateBase: NSObjectProtocol { + /// Called when the user selects a new shipping method. The delegate should determine + /// shipping costs based on the shipping method and either the shipping address supplied in the original + /// PKPaymentRequest or the address fragment provided by the last call to paymentAuthorizationController: + /// didSelectShippingContact:completion:. + /// You must invoke the completion block with an updated array of PKPaymentSummaryItem objects. + @objc(applePayContext:didSelectShippingMethod:handler:) + optional func applePayContext( + _ context: STPApplePayContext, + didSelect shippingMethod: PKShippingMethod, + handler: @escaping (_ update: PKPaymentRequestShippingMethodUpdate) -> Void + ) + + /// Called when the user has selected a new shipping address. You should inspect the + /// address and must invoke the completion block with an updated array of PKPaymentSummaryItem objects. + /// @note To maintain privacy, the shipping information is anonymized. For example, in the United States it only includes the city, state, and zip code. This provides enough information to calculate shipping costs, without revealing sensitive information until the user actually approves the purchase. + /// Receive full shipping information in the paymentInformation passed to `applePayContext:didCreatePaymentMethod:paymentInformation:completion:` + @objc optional func applePayContext( + _ context: STPApplePayContext, + didSelectShippingContact contact: PKContact, + handler: @escaping (_ update: PKPaymentRequestShippingContactUpdate) -> Void + ) +} + +/// Implement the required methods of this delegate to supply a PaymentIntent to ApplePayContext and be notified of the completion of the Apple Pay payment. /// You may also implement the optional delegate methods to handle shipping methods and shipping address changes e.g. to verify you can ship to the address, or update the payment amount. -@available(iOSApplicationExtension, unavailable) -@available(macCatalystApplicationExtension, unavailable) -@objc public protocol STPApplePayContextDelegate: NSObjectProtocol { +public protocol ApplePayContextDelegate: _stpinternal_STPApplePayContextDelegateBase { /// Called after the customer has authorized Apple Pay. Implement this method to call the completion block with the client secret of a PaymentIntent or SetupIntent. /// - Parameters: /// - paymentMethod: The PaymentMethod that represents the customer's Apple Pay payment method. - /// If you create the PaymentIntent with confirmation_method=manual, pass `paymentMethod.stripeId` as the payment_method and confirm=true. Otherwise, you can ignore this parameter. + /// If you create the PaymentIntent with confirmation_method=manual, pass `paymentMethod.id` as the payment_method and confirm=true. Otherwise, you can ignore this parameter. /// - paymentInformation: The underlying PKPayment created by Apple Pay. /// If you create the PaymentIntent with confirmation_method=manual, you can collect shipping information using its `shippingContact` and `shippingMethod` properties. /// - completion: Call this with the PaymentIntent or SetupIntent client secret, or the error that occurred creating the PaymentIntent or SetupIntent. func applePayContext( _ context: STPApplePayContext, - didCreatePaymentMethod paymentMethod: STPPaymentMethod, + didCreatePaymentMethod paymentMethod: StripeAPI.PaymentMethod, paymentInformation: PKPayment, completion: @escaping STPIntentClientSecretCompletionBlock ) @@ -35,34 +58,11 @@ import PassKit /// - Parameters: /// - status: The status of the payment /// - error: The error that occurred, if any. - @objc(applePayContext:didCompleteWithStatus:error:) func applePayContext( _ context: STPApplePayContext, - didCompleteWith status: STPPaymentStatus, + didCompleteWith status: STPApplePayContext.PaymentStatus, error: Error? ) - - /// Called when the user selects a new shipping method. The delegate should determine - /// shipping costs based on the shipping method and either the shipping address supplied in the original - /// PKPaymentRequest or the address fragment provided by the last call to paymentAuthorizationController: - /// didSelectShippingContact:completion:. - /// You must invoke the completion block with an updated array of PKPaymentSummaryItem objects. - @objc(applePayContext:didSelectShippingMethod:handler:) - optional func applePayContext( - _ context: STPApplePayContext, - didSelect shippingMethod: PKShippingMethod, - handler: @escaping (_ update: PKPaymentRequestShippingMethodUpdate) -> Void - ) - - /// Called when the user has selected a new shipping address. You should inspect the - /// address and must invoke the completion block with an updated array of PKPaymentSummaryItem objects. - /// @note To maintain privacy, the shipping information is anonymized. For example, in the United States it only includes the city, state, and zip code. This provides enough information to calculate shipping costs, without revealing sensitive information until the user actually approves the purchase. - /// Receive full shipping information in the paymentInformation passed to `applePayContext:didCreatePaymentMethod:paymentInformation:completion:` - @objc optional func applePayContext( - _ context: STPApplePayContext, - didSelectShippingContact contact: PKContact, - handler: @escaping (_ update: PKPaymentRequestShippingContactUpdate) -> Void - ) } /// A helper class that implements Apple Pay. @@ -74,16 +74,15 @@ import PassKit /// 5. After payment completes/errors and the sheet is dismissed, this class informs you in the applePayContext:didCompleteWithStatus: delegate method /// - seealso: https://stripe.com/docs/apple-pay#native for a full guide /// - seealso: ApplePayExampleViewController for an example -@available(iOSApplicationExtension, unavailable) -@available(macCatalystApplicationExtension, unavailable) -@objc public class STPApplePayContext: NSObject, PKPaymentAuthorizationControllerDelegate { +@objc(_stpinternal_APContextSwift) +public class STPApplePayContext: NSObject, PKPaymentAuthorizationControllerDelegate { /// Initializes this class. /// @note This may return nil if the request is invalid e.g. the user is restricted by parental controls, or can't make payments on any of the request's supported networks + /// @note If using Swift, using ApplePayContextDelegate is recommended over STPApplePayContextDelegate. /// - Parameters: /// - paymentRequest: The payment request to use with Apple Pay. /// - delegate: The delegate. - @objc(initWithPaymentRequest:delegate:) - public required init?(paymentRequest: PKPaymentRequest, delegate: STPApplePayContextDelegate?) { + public required init?(paymentRequest: PKPaymentRequest, delegate: _stpinternal_STPApplePayContextDelegateBase?) { STPAnalyticsClient.sharedClient.addClass(toProductUsageIfNecessary: STPApplePayContext.self) if !StripeAPI.canSubmitPaymentRequest(paymentRequest) { return nil @@ -99,31 +98,42 @@ import PassKit super.init() authorizationController?.delegate = self } - - private var presentationWindow: UIWindow? - /// Presents the Apple Pay sheet from the specified view controller, starting the payment process. - /// @note This method should only be called once; create a new instance of STPApplePayContext every time you present Apple Pay. - /// @deprecated A presenting UIViewController is no longer needed. Use presentApplePay(completion:) instead. + + /// Initializes this class. + /// @note This may return nil if the request is invalid e.g. the user is restricted by parental controls, or can't make payments on any of the request's supported networks /// - Parameters: - /// - viewController: The UIViewController instance to present the Apple Pay sheet on - /// - completion: Called after the Apple Pay sheet is presented - @objc(presentApplePayOnViewController:completion:) - @available( - *, deprecated, message: "Use `presentApplePay(completion:)` instead.", - renamed: "presentApplePay(completion:)" - ) - public func presentApplePay( - on viewController: UIViewController, completion: STPVoidBlock? = nil - ) { - let window = viewController.viewIfLoaded?.window - self.presentApplePay(from: window, completion: completion) + /// - paymentRequest: The payment request to use with Apple Pay. + /// - delegate: The delegate. + public required init?(paymentRequest: PKPaymentRequest, delegate: ApplePayContextDelegate) { + STPAnalyticsClient.sharedClient.addClass(toProductUsageIfNecessary: STPApplePayContext.self) + STPAnalyticsClient.sharedClient.addClass(toProductUsageIfNecessary: ModernApplePayContext.self) + if !StripeAPI.canSubmitPaymentRequest(paymentRequest) { + return nil + } + + authorizationController = PKPaymentAuthorizationController(paymentRequest: paymentRequest) + if authorizationController == nil { + return nil + } + + self.delegate = delegate + + super.init() + authorizationController?.delegate = self } + /// This is used internally to hold a reference to the Objective-C bridge, + /// as we attach it to the Apple Pay sheet to keep the bridge from dealloc-ing + /// while presented. + /// :nodoc: + @_spi(STP) public weak var applePayContextObjCBridge: NSObject? + + private var presentationWindow: UIWindow? + /// Presents the Apple Pay sheet from the key window, starting the payment process. /// @note This method should only be called once; create a new instance of STPApplePayContext every time you present Apple Pay. /// - Parameters: /// - completion: Called after the Apple Pay sheet is presented - @objc(presentApplePayWithCompletion:) @available( iOSApplicationExtension, unavailable, message: "Use `presentApplePay(from:completion:)` in App Extensions." @@ -142,7 +152,6 @@ import PassKit /// - Parameters: /// - window: The UIWindow to host the Apple Pay sheet /// - completion: Called after the Apple Pay sheet is presented - @objc(presentApplePayFromWindow:withCompletion:) public func presentApplePay(from window: UIWindow?, completion: STPVoidBlock? = nil) { presentationWindow = window guard !didPresentApplePay, let applePayController = self.authorizationController else { @@ -154,38 +163,48 @@ import PassKit } didPresentApplePay = true - // This instance must live so that the apple pay sheet is dismissed; until then, the app is effectively frozen. + // This instance (and the associated Objective-C bridge object, if any) must live so + // that the apple pay sheet is dismissed; until then, the app is effectively frozen. objc_setAssociatedObject( - applePayController, UnsafeRawPointer(&kSTPApplePayContextAssociatedObjectKey), self, + applePayController, &kApplePayContextAssociatedObjectKey, self, + .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + objc_setAssociatedObject( + applePayController, &kApplePayContextObjcBridgeAssociatedObjectKey, applePayContextObjCBridge, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) applePayController.present { (presented) in - stpDispatchToMainThreadIfNecessary { + DispatchQueue.main.async { completion?() } } } - /// The STPAPIClient instance to use to make API requests to Stripe. - /// Defaults to `STPAPIClient.shared`. - public var apiClient: STPAPIClient = .shared - - /// The STPAPIClient instance to use to make API requests to Stripe. - /// Defaults to `STPAPIClient.shared`. - @available(swift, deprecated: 0.0.1, renamed: "apiClient") - @objc(apiClient) public var _objc_apiClient: _stpobjc_STPAPIClient { - get { - _stpobjc_STPAPIClient(apiClient: apiClient) - } - set { - apiClient = newValue._apiClient - } + /// Presents the Apple Pay sheet from the specified view controller, starting the payment process. + /// @note This method should only be called once; create a new instance of STPApplePayContext every time you present Apple Pay. + /// @deprecated A presenting UIViewController is no longer needed. Use presentApplePay(completion:) instead. + /// - Parameters: + /// - viewController: The UIViewController instance to present the Apple Pay sheet on + /// - completion: Called after the Apple Pay sheet is presented + @objc(presentApplePayOnViewController:completion:) + @available( + *, deprecated, message: "Use `presentApplePay(completion:)` instead.", + renamed: "presentApplePay(completion:)" + ) + public func presentApplePay( + on viewController: UIViewController, completion: STPVoidBlock? = nil + ) { + let window = viewController.viewIfLoaded?.window + presentApplePay(from: window, completion: completion) } - private weak var delegate: STPApplePayContextDelegate? + /// The API Client to use to make requests. + /// Defaults to `STPAPIClient.shared` + public var apiClient: STPAPIClient = STPAPIClient.shared + + private weak var delegate: _stpinternal_STPApplePayContextDelegateBase? @objc var authorizationController: PKPaymentAuthorizationController? // Internal state - private var paymentState: STPPaymentState = .notStarted + private var paymentState: PaymentState = .notStarted private var error: Error? /// YES if the flow cancelled or timed out. This toggles which delegate method (didFinish or didAuthorize) calls our didComplete delegate method private var didCancelOrTimeoutWhilePending = false @@ -193,7 +212,7 @@ import PassKit /// :nodoc: public override func responds(to aSelector: Selector!) -> Bool { - // STPApplePayContextDelegate exposes methods that map 1:1 to PKPaymentAuthorizationControllerDelegate methods + // ApplePayContextDelegate exposes methods that map 1:1 to PKPaymentAuthorizationControllerDelegate methods // We want this method to return YES for these methods IFF they are implemented by our delegate // Why not simply implement the methods to call their equivalents on self.delegate? @@ -220,12 +239,12 @@ import PassKit (PKPaymentAuthorizationControllerDelegate.paymentAuthorizationController( _:didSelectShippingMethod:handler:)) as pkDidSelectShippingMethodSignature) let stp_didSelectShippingMethod = #selector( - STPApplePayContextDelegate.applePayContext(_:didSelect:handler:)) + _stpinternal_STPApplePayContextDelegateBase.applePayContext(_:didSelect:handler:)) let pk_didSelectShippingContact = #selector( PKPaymentAuthorizationControllerDelegate.paymentAuthorizationController( _:didSelectShippingContact:handler:)) let stp_didSelectShippingContact = #selector( - STPApplePayContextDelegate.applePayContext(_:didSelectShippingContact:handler:)) + _stpinternal_STPApplePayContextDelegateBase.applePayContext(_:didSelectShippingContact:handler:)) return [ pk_didSelectShippingMethod: stp_didSelectShippingMethod, @@ -236,33 +255,31 @@ import PassKit func _end() { if let authorizationController = authorizationController { objc_setAssociatedObject( - authorizationController, UnsafeRawPointer(&kSTPApplePayContextAssociatedObjectKey), + authorizationController, &kApplePayContextAssociatedObjectKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + objc_setAssociatedObject( + authorizationController, &kApplePayContextObjcBridgeAssociatedObjectKey, nil, + .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } authorizationController = nil delegate = nil } - func _shippingDetails(from payment: PKPayment) -> STPPaymentIntentShippingDetailsParams? { + func _shippingDetails(from payment: PKPayment) -> StripeAPI.PaymentIntentParams.ShippingDetails? { guard let address = payment.shippingContact?.postalAddress, let name = payment.shippingContact?.name else { - // The shipping address street and name are required parameters for a valid STPPaymentIntentShippingDetailsParams + // The shipping address street and name are required parameters for a valid PaymentIntentParams.ShippingDetails return nil } - let addressParams = STPPaymentIntentShippingDetailsAddressParams(line1: address.street) - addressParams.city = address.city - addressParams.state = address.state - addressParams.country = address.isoCountryCode - addressParams.postalCode = address.postalCode + let addressParams = StripeAPI.PaymentIntentParams.ShippingDetails.Address(city: address.city, country: address.isoCountryCode, line1: address.street, postalCode: address.postalCode, state: address.state) let formatter = PersonNameComponentsFormatter() formatter.style = .long - let shippingParams = STPPaymentIntentShippingDetailsParams( - address: addressParams, name: formatter.string(from: name)) - shippingParams.phone = payment.shippingContact?.phoneNumber?.stringValue + let shippingParams = StripeAPI.PaymentIntentParams.ShippingDetails( + address: addressParams, name: formatter.string(from: name), phone: payment.shippingContact?.phoneNumber?.stringValue) return shippingParams } @@ -293,7 +310,7 @@ import PassKit handler completion: @escaping (PKPaymentRequestShippingMethodUpdate) -> Void ) { if delegate?.responds( - to: #selector(STPApplePayContextDelegate.applePayContext(_:didSelect:handler:))) + to: #selector(_stpinternal_STPApplePayContextDelegateBase.applePayContext(_:didSelect:handler:))) ?? false { delegate?.applePayContext?(self, didSelect: shippingMethod, handler: completion) @@ -308,7 +325,7 @@ import PassKit ) { if delegate?.responds( to: #selector( - STPApplePayContextDelegate.applePayContext(_:didSelectShippingContact:handler:)) + _stpinternal_STPApplePayContextDelegateBase.applePayContext(_:didSelectShippingContact:handler:)) ) ?? false { delegate?.applePayContext?(self, didSelectShippingContact: contact, handler: completion) } @@ -324,8 +341,7 @@ import PassKit case .notStarted: controller.dismiss { stpDispatchToMainThreadIfNecessary { - self.delegate?.applePayContext( - self, didCompleteWith: .userCancellation, error: nil) + self.callDidCompleteDelegate(status: .userCancellation, error: nil) self._end() } } @@ -336,21 +352,20 @@ import PassKit case .error: controller.dismiss { stpDispatchToMainThreadIfNecessary { - self.delegate?.applePayContext(self, didCompleteWith: .error, error: self.error) + self.callDidCompleteDelegate(status: .error, error: self.error) self._end() } } case .success: controller.dismiss { stpDispatchToMainThreadIfNecessary { - self.delegate?.applePayContext(self, didCompleteWith: .success, error: nil) + self.callDidCompleteDelegate(status: .success, error: nil) self._end() } } } } - /// :nodoc: public func presentationWindow(for controller: PKPaymentAuthorizationController) -> UIWindow? { return presentationWindow } @@ -361,16 +376,15 @@ import PassKit completion: @escaping (PKPaymentAuthorizationStatus, Error?) -> Void ) { // Helper to handle annoying logic around "Do I call completion block or dismiss + call delegate?" - let handleFinalState: ((STPPaymentState, Error?) -> Void) = { state, error in + let handleFinalState: ((PaymentState, Error?) -> Void) = { state, error in switch state { case .error: self.paymentState = .error self.error = error if self.didCancelOrTimeoutWhilePending { self.authorizationController?.dismiss { - stpDispatchToMainThreadIfNecessary { - self.delegate?.applePayContext( - self, didCompleteWith: .error, error: self.error) + DispatchQueue.main.async { + self.callDidCompleteDelegate(status: .error, error: self.error) self._end() } } @@ -382,9 +396,8 @@ import PassKit self.paymentState = .success if self.didCancelOrTimeoutWhilePending { self.authorizationController?.dismiss { - stpDispatchToMainThreadIfNecessary { - self.delegate?.applePayContext( - self, didCompleteWith: .success, error: nil) + DispatchQueue.main.async { + self.callDidCompleteDelegate(status: .success, error: nil) self._end() } } @@ -399,140 +412,213 @@ import PassKit } // 1. Create PaymentMethod - apiClient.createPaymentMethod(with: payment) { paymentMethod, paymentMethodCreationError in - guard let paymentMethod = paymentMethod, paymentMethodCreationError == nil, - self.authorizationController != nil - else { - handleFinalState(.error, paymentMethodCreationError) + StripeAPI.PaymentMethod.create(apiClient: apiClient, payment: payment) { result in + guard let paymentMethod = try? result.get(), self.authorizationController != nil else { + if case .failure(let error) = result { + handleFinalState(.error, error) + } else { + handleFinalState(.error, nil) + } return } - // 2. Fetch PaymentIntent/SetupIntent client secret from delegate - self.delegate?.applePayContext( - self, didCreatePaymentMethod: paymentMethod, paymentInformation: payment - ) { clientSecret, intentCreationError in - guard let clientSecret = clientSecret, intentCreationError == nil, - self.authorizationController != nil - else { - handleFinalState(.error, intentCreationError) - return - } + let paymentMethodCompletion : STPIntentClientSecretCompletionBlock = { + clientSecret, intentCreationError in + guard let clientSecret = clientSecret, intentCreationError == nil, + self.authorizationController != nil + else { + handleFinalState(.error, intentCreationError) + return + } - if STPSetupIntentConfirmParams.isClientSecretValid(clientSecret) { - // 3a. Retrieve the SetupIntent and see if we need to confirm it client-side - self.apiClient.retrieveSetupIntent(withClientSecret: clientSecret) { - setupIntent, error in - guard let setupIntent = setupIntent, error == nil, - self.authorizationController != nil - else { - handleFinalState(.error, error) - return - } - switch setupIntent.status { - case .requiresConfirmation, .requiresAction, .requiresPaymentMethod: - // 4a. Confirm the SetupIntent - self.paymentState = .pending // After this point, we can't cancel - let confirmParams = STPSetupIntentConfirmParams( - clientSecret: clientSecret) - confirmParams.paymentMethodID = paymentMethod.stripeId - confirmParams.useStripeSDK = true - - self.apiClient.confirmSetupIntent(with: confirmParams) { - (setupIntent, error) in - guard let setupIntent = setupIntent, error == nil, - self.authorizationController != nil, - setupIntent.status == .succeeded - else { + if StripeAPI.SetupIntentConfirmParams.isClientSecretValid(clientSecret) { + // 3a. Retrieve the SetupIntent and see if we need to confirm it client-side + StripeAPI.SetupIntent.get(apiClient: self.apiClient, clientSecret: clientSecret) { + result in + guard let setupIntent = try? result.get(), self.authorizationController != nil else { + if case .failure(let error) = result { handleFinalState(.error, error) - return + } else { + handleFinalState(.error, nil) + } + return + } + + switch setupIntent.status { + case .requiresConfirmation, .requiresAction, .requiresPaymentMethod: + // 4a. Confirm the SetupIntent + self.paymentState = .pending // After this point, we can't cancel + var confirmParams = StripeAPI.SetupIntentConfirmParams( + clientSecret: clientSecret) + confirmParams.paymentMethod = paymentMethod.id + confirmParams.useStripeSdk = true + + StripeAPI.SetupIntent.confirm(apiClient: self.apiClient, params: confirmParams) { + result in + guard let setupIntent = try? result.get(), self.authorizationController != nil, + setupIntent.status == .succeeded + else { + if case .failure(let error) = result { + handleFinalState(.error, error) + } else { + handleFinalState(.error, nil) + } + return + } + + handleFinalState(.success, nil) } + case .succeeded: handleFinalState(.success, nil) + case .canceled, .processing, .unknown, .unparsable, .none: + handleFinalState( + .error, + Self.makeUnknownError( + message: + "The SetupIntent is in an unexpected state: \(setupIntent.status!)" + )) } - case .succeeded: - handleFinalState(.success, nil) - case .canceled, .processing, .unknown: - handleFinalState( - .error, - Self.makeUnknownError( - message: - "The SetupIntent is in an unexpected state: \(setupIntent.status)" - )) - } - } - } else { - let paymentIntentClientSecret = clientSecret - // 3b. Retrieve the PaymentIntent and see if we need to confirm it client-side - self.apiClient.retrievePaymentIntent( - withClientSecret: paymentIntentClientSecret - ) { paymentIntent, error in - guard let paymentIntent = paymentIntent, error == nil, - self.authorizationController != nil - else { - handleFinalState(.error, error) - return } - if paymentIntent.confirmationMethod == .automatic - && (paymentIntent.status == .requiresPaymentMethod - || paymentIntent.status == .requiresConfirmation) - { - // 4b. Confirm the PaymentIntent - let paymentIntentParams = STPPaymentIntentParams( - clientSecret: paymentIntentClientSecret) - paymentIntentParams.paymentMethodId = paymentMethod.stripeId - paymentIntentParams.useStripeSDK = NSNumber(value: true) - paymentIntentParams.shipping = self._shippingDetails(from: payment) - - self.paymentState = .pending // After this point, we can't cancel - - // We don't use PaymentHandler because we can't handle next actions as-is - we'd need to dismiss the Apple Pay VC. - self.apiClient.confirmPaymentIntent(with: paymentIntentParams) { - postConfirmPI, confirmError in - if let postConfirmPI = postConfirmPI, - postConfirmPI.status == .succeeded - || postConfirmPI.status == .requiresCapture - { - handleFinalState(.success, nil) + } else { + let paymentIntentClientSecret = clientSecret + // 3b. Retrieve the PaymentIntent and see if we need to confirm it client-side + StripeAPI.PaymentIntent.get( + apiClient: self.apiClient, + clientSecret: paymentIntentClientSecret + ) { result in + guard let paymentIntent = try? result.get(), self.authorizationController != nil + else { + if case .failure(let error) = result { + handleFinalState(.error, error) } else { - handleFinalState(.error, confirmError) + handleFinalState(.error, nil) } + return + } + + if paymentIntent.confirmationMethod == .automatic + && (paymentIntent.status == .requiresPaymentMethod + || paymentIntent.status == .requiresConfirmation) + { + // 4b. Confirm the PaymentIntent + + var paymentIntentParams = StripeAPI.PaymentIntentParams( + clientSecret: paymentIntentClientSecret) + paymentIntentParams.paymentMethod = paymentMethod.id + paymentIntentParams.useStripeSdk = true + paymentIntentParams.shipping = self._shippingDetails(from: payment) + + self.paymentState = .pending // After this point, we can't cancel + + // We don't use PaymentHandler because we can't handle next actions as-is - we'd need to dismiss the Apple Pay VC. + StripeAPI.PaymentIntent.confirm(apiClient: self.apiClient, params: paymentIntentParams) { + result in + guard let postConfirmPI = try? result.get(), + postConfirmPI.status == .succeeded || postConfirmPI.status == .requiresCapture + else { + if case .failure(let error) = result { + handleFinalState(.error, error) + } else { + handleFinalState(.error, nil) + } + return + } + handleFinalState(.success, nil) + } + } else if paymentIntent.status == .succeeded + || paymentIntent.status == .requiresCapture + { + handleFinalState(.success, nil) + } else { + let unknownError = Self.makeUnknownError( + message: + "The PaymentIntent is in an unexpected state. If you pass confirmation_method = manual when creating the PaymentIntent, also pass confirm = true. If server-side confirmation fails, double check you are passing the error back to the client." + ) + handleFinalState(.error, unknownError) } - } else if paymentIntent.status == .succeeded - || paymentIntent.status == .requiresCapture - { - handleFinalState(.success, nil) - } else { - let unknownError = Self.makeUnknownError( - message: - "The PaymentIntent is in an unexpected state. If you pass confirmation_method = manual when creating the PaymentIntent, also pass confirm = true. If server-side confirmation fails, double check you are passing the error back to the client." - ) - handleFinalState(.error, unknownError) } } } + // 2. Fetch PaymentIntent/SetupIntent client secret from delegate + let legacyDelegateSelector = NSSelectorFromString("applePayContext:didCreatePaymentMethod:paymentInformation:completion:") + if let delegate = self.delegate { + if let delegate = delegate as? ApplePayContextDelegate { + delegate.applePayContext( + self, didCreatePaymentMethod: paymentMethod, paymentInformation: payment, completion: paymentMethodCompletion) + } else if delegate.responds(to: legacyDelegateSelector), + let helperClass = NSClassFromString("STPApplePayContextLegacyHelper") { + let legacyStorage = _stpinternal_ApplePayContextDidCreatePaymentMethodStorage(delegate: delegate, context: self, paymentMethod: paymentMethod, paymentInformation: payment, completion: paymentMethodCompletion) + helperClass.performDidCreatePaymentMethod(legacyStorage) + } else { + assertionFailure("An STPApplePayContext's delegate must conform to ApplePayContextDelegate or STPApplePayContextDelegate.") + } } } } + func callDidCompleteDelegate(status: PaymentStatus, error: Error?) { + if let delegate = self.delegate { + if let delegate = delegate as? ApplePayContextDelegate { + delegate.applePayContext(self, didCompleteWith: status, error: error) + } else if delegate.responds(to: NSSelectorFromString("applePayContext:didCompleteWithStatus:error:")) { + if let helperClass = NSClassFromString("STPApplePayContextLegacyHelper") { + let legacyStorage = _stpinternal_ApplePayContextDidCompleteStorage(delegate: delegate, context: self, status: status, error: error) + helperClass.performDidComplete(legacyStorage) + } + } else { + assertionFailure("An STPApplePayContext's delegate must conform to ApplePayContextDelegate or STPApplePayContextDelegate.") + } + } + + } + static func makeUnknownError(message: String) -> NSError { let userInfo = [ NSLocalizedDescriptionKey: NSError.stp_unexpectedErrorMessage(), STPError.errorMessageKey: message, ] return NSError( - domain: STPPaymentHandler.errorDomain, - code: STPPaymentHandlerErrorCode.intentStatusErrorCode.rawValue, userInfo: userInfo) + domain: STPError.STPPaymentHandlerErrorDomain, + code: STPPaymentHandlerErrorCodeIntentStatusErrorCode, userInfo: userInfo) + } + + /// This is STPPaymentHandlerErrorCode.intentStatusErrorCode.rawValue, which we don't want to vend from this framework. + fileprivate static let STPPaymentHandlerErrorCodeIntentStatusErrorCode = 3 + + private var kApplePayContextAssociatedObjectKey = 0 + private var kApplePayContextObjcBridgeAssociatedObjectKey = 0 + enum PaymentState { + case notStarted + case pending + case error + case success + } + + /// An enum representing the status of a payment requested from the user. + @frozen public enum PaymentStatus { + /// The payment succeeded. + case success + /// The payment failed due to an unforeseen error, such as the user's Internet connection being offline. + case error + /// The user cancelled the payment (for example, by hitting "cancel" in the Apple Pay dialog). + case userCancellation } } /// :nodoc: -@available(iOSApplicationExtension, unavailable) -@available(macCatalystApplicationExtension, unavailable) @_spi(STP) extension STPApplePayContext: STPAnalyticsProtocol { @_spi(STP) public static var stp_analyticsIdentifier: String { return "STPApplePayContext" } } +/// :nodoc: +class ModernApplePayContext: STPAnalyticsProtocol { + @_spi(STP) public static var stp_analyticsIdentifier: String { + return "ModernApplePayContext" + } +} + private var kSTPApplePayContextAssociatedObjectKey = 0 enum STPPaymentState: Int { case notStarted @@ -540,3 +626,12 @@ enum STPPaymentState: Int { case error case success } + +fileprivate class _stpinternal_STPApplePayContextLegacyHelper: NSObject { + @objc class func performDidCreatePaymentMethod(_ storage: _stpinternal_ApplePayContextDidCreatePaymentMethodStorage) { + // Placeholder to allow this to be called on AnyObject + } + @objc class func performDidComplete(_ storage: _stpinternal_ApplePayContextDidCompleteStorage) { + // Placeholder to allow this to be called on AnyObject + } +} diff --git a/StripeApplePay/StripeApplePay/Source/Blocks.swift b/StripeApplePay/StripeApplePay/Source/Blocks.swift new file mode 100644 index 00000000000..f1053b3bb15 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/Blocks.swift @@ -0,0 +1,17 @@ +// +// Blocks.swift +// StripeApplePay +// +// Created by David Estes on 1/6/22. +// + +import Foundation + +/// An empty block, called with no arguments, returning nothing. +public typealias STPVoidBlock = () -> Void + +/// A block to be run with the client secret of a PaymentIntent or SetupIntent. +/// - Parameters: +/// - clientSecret: The client secret of the PaymentIntent or SetupIntent. See https://stripe.com/docs/api/payment_intents/object#payment_intent_object-client_secret +/// - error: The error that occurred when creating the Intent, or nil if none occurred. +public typealias STPIntentClientSecretCompletionBlock = (String?, Error?) -> Void diff --git a/StripeApplePay/StripeApplePay/Source/Extensions/BillingDetails+ApplePay.swift b/StripeApplePay/StripeApplePay/Source/Extensions/BillingDetails+ApplePay.swift new file mode 100644 index 00000000000..cfc1ae9ae3f --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/Extensions/BillingDetails+ApplePay.swift @@ -0,0 +1,95 @@ +// +// BillingDetails+ApplePay.swift +// StripeiOS +// +// Created by David Estes on 8/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit +@_spi(STP) import StripeCore + +extension StripeContact { + /// Initializes a new Contact with data from an PassKit contact. + /// - Parameter contact: The PassKit contact you want to populate the Contact from. + /// - Returns: A new Contact with data copied from the passed in contact. + init(pkContact contact: PKContact) { + let nameComponents = contact.name + if let nameComponents = nameComponents { + givenName = stringIfHasContentsElseNil(nameComponents.givenName) + familyName = stringIfHasContentsElseNil(nameComponents.familyName) + + name = stringIfHasContentsElseNil( + PersonNameComponentsFormatter.localizedString(from: nameComponents, style: .default) + ) + } + email = stringIfHasContentsElseNil(contact.emailAddress) + if let phoneNumber = contact.phoneNumber { + phone = sanitizedPhoneString(from: phoneNumber) + } else { + phone = nil + } + setAddressFromCNPostal(contact.postalAddress) + } + + private func sanitizedPhoneString(from phoneNumber: CNPhoneNumber) -> String? { + return stringIfHasContentsElseNil( + STPNumericStringValidator.sanitizedNumericString(for: phoneNumber.stringValue)) + } + + private mutating func setAddressFromCNPostal(_ address: CNPostalAddress?) { + line1 = stringIfHasContentsElseNil(address?.street) + city = stringIfHasContentsElseNil(address?.city) + state = stringIfHasContentsElseNil(address?.state) + postalCode = stringIfHasContentsElseNil(address?.postalCode) + country = stringIfHasContentsElseNil(address?.isoCountryCode.uppercased()) + } +} + + +extension StripeAPI.BillingDetails { + init?(from payment: PKPayment) { + var billingDetails: StripeAPI.BillingDetails? + if payment.billingContact != nil { + billingDetails = StripeAPI.BillingDetails() + if let billingContact = payment.billingContact { + let billingAddress = StripeContact(pkContact: billingContact) + billingDetails?.name = billingAddress.name + billingDetails?.email = billingAddress.email + billingDetails?.phone = billingAddress.phone + if billingContact.postalAddress != nil { + billingDetails?.address = StripeAPI.BillingDetails.Address(contact: billingAddress) + } + } + } + + // The phone number and email in the "Contact" panel in the Apple Pay dialog go into the shippingContact, + // not the billingContact. To work around this, we should fill the billingDetails' email and phone + // number from the shippingDetails. + if payment.shippingContact != nil { + var shippingAddress: StripeContact? + if let shippingContact = payment.shippingContact { + shippingAddress = StripeContact(pkContact: shippingContact) + } + if billingDetails?.email == nil && shippingAddress?.email != nil { + if billingDetails == nil { + billingDetails = StripeAPI.BillingDetails() + } + billingDetails?.email = shippingAddress?.email + } + if billingDetails?.phone == nil && shippingAddress?.phone != nil { + if billingDetails == nil { + billingDetails = StripeAPI.BillingDetails() + } + billingDetails?.phone = shippingAddress?.phone + } + } + + if let billingDetails = billingDetails { + self = billingDetails + } else { + return nil + } + } +} diff --git a/StripeApplePay/StripeApplePay/Source/Extensions/PKContact+Stripe.swift b/StripeApplePay/StripeApplePay/Source/Extensions/PKContact+Stripe.swift new file mode 100644 index 00000000000..5d46914ced1 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/Extensions/PKContact+Stripe.swift @@ -0,0 +1,25 @@ +// +// PKContact+Stripe.swift +// StripeApplePay +// +// Created by David Estes on 11/16/21. +// + +import Foundation +import PassKit + +extension PKContact { + @_spi(STP) public var addressParams: [AnyHashable: Any] { + var params: [AnyHashable: Any] = [:] + let stpAddress = StripeContact(pkContact: self) + + params["name"] = stpAddress.name + params["address_line1"] = stpAddress.line1 + params["address_city"] = stpAddress.city + params["address_state"] = stpAddress.state + params["address_zip"] = stpAddress.postalCode + params["address_country"] = stpAddress.country + + return params + } +} diff --git a/Stripe/PKPayment+Stripe.swift b/StripeApplePay/StripeApplePay/Source/Extensions/PKPayment+Stripe.swift similarity index 87% rename from Stripe/PKPayment+Stripe.swift rename to StripeApplePay/StripeApplePay/Source/Extensions/PKPayment+Stripe.swift index c6d1fb7d7eb..5b3e54cda0c 100644 --- a/Stripe/PKPayment+Stripe.swift +++ b/StripeApplePay/StripeApplePay/Source/Extensions/PKPayment+Stripe.swift @@ -9,12 +9,12 @@ import PassKit extension PKPayment { /// Returns true if the instance is a payment from the simulator. - @objc func stp_isSimulated() -> Bool { + @_spi(STP) public func stp_isSimulated() -> Bool { return token.transactionIdentifier == "Simulated Identifier" } /// Returns a fake transaction identifier with the expected ~-separated format. - @objc class func stp_testTransactionIdentifier() -> String? { + @_spi(STP) public class func stp_testTransactionIdentifier() -> String? { var uuid = UUID().uuidString uuid = uuid.replacingOccurrences(of: "~", with: "") diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/Address.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/Address.swift new file mode 100644 index 00000000000..3847cd80463 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/Address.swift @@ -0,0 +1,43 @@ +// +// Address.swift +// StripeiOS +// +// Created by David Estes on 8/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +/// An internal struct for handling contacts. This is not encodable/decodable for use with the Stripe API. +struct StripeContact { + /// The user's full name (e.g. "Jane Doe") + public var name: String? + + /// The first line of the user's street address (e.g. "123 Fake St") + public var line1: String? + + /// The apartment, floor number, etc of the user's street address (e.g. "Apartment 1A") + public var line2: String? + + /// The city in which the user resides (e.g. "San Francisco") + public var city: String? + + /// The state in which the user resides (e.g. "CA") + public var state: String? + + /// The postal code in which the user resides (e.g. "90210") + public var postalCode: String? + + /// The ISO country code of the address (e.g. "US") + public var country: String? + + /// The phone number of the address (e.g. "8885551212") + public var phone: String? + + /// The email of the address (e.g. "jane@doe.com") + public var email: String? + + internal var givenName: String? + internal var familyName: String? +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/BillingDetails.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/BillingDetails.swift new file mode 100644 index 00000000000..608e5d440d9 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/BillingDetails.swift @@ -0,0 +1,67 @@ +// +// BillingDetails.swift +// StripeiOS +// +// Created by David Estes on 7/15/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + /// Billing information associated with a `STPPaymentMethod` that may be used or required by particular types of payment methods. + /// - seealso: https://stripe.com/docs/api/payment_methods/object#payment_method_object-billing_details + public struct BillingDetails: StripeCodable { + /// Billing address. + @IncludeUnknownFields + public var address: Address? + + /// The billing address, a property sent in a PaymentMethod response + public struct Address: StripeCodable { + /// The first line of the user's street address (e.g. "123 Fake St") + public var line1: String? + + /// The apartment, floor number, etc of the user's street address (e.g. "Apartment 1A") + public var line2: String? + + /// The city in which the user resides (e.g. "San Francisco") + public var city: String? + + /// The state in which the user resides (e.g. "CA") + public var state: String? + + /// The postal code in which the user resides (e.g. "90210") + public var postalCode: String? + + /// The ISO country code of the address (e.g. "US") + public var country: String? + + public var _additionalParametersStorage: NonEncodableParameters? + public var _allResponseFieldsStorage: NonEncodableParameters? + } + + /// Email address. + public var email: String? + /// Full name. + public var name: String? + /// Billing phone number (including extension). + public var phone: String? + + public var _additionalParametersStorage: NonEncodableParameters? + public var _allResponseFieldsStorage: NonEncodableParameters? + } + +} + +extension StripeAPI.BillingDetails.Address { + init(contact: StripeContact) { + self.city = contact.city + self.country = contact.country + self.line1 = contact.line1 + self.line2 = contact.line2 + self.postalCode = contact.postalCode + self.state = contact.state + } +} + diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentIntent.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentIntent.swift new file mode 100644 index 00000000000..1964d2aaa44 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentIntent.swift @@ -0,0 +1,142 @@ +// +// PaymentIntent.swift +// StripeiOS +// +// Created by David Estes on 6/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + @_spi(STP) public struct PaymentIntent: StripeDecodable { + // TODO: (MOBILESDK-468) Add modern bindings for more PaymentIntent fields + /// The Stripe ID of the PaymentIntent. + @_spi(STP) public let id: String + + /// The client secret used to fetch this PaymentIntent + @_spi(STP) public let clientSecret: String + + /// Amount intended to be collected by this PaymentIntent. + @_spi(STP) public let amount: Int + + /// If status is `.canceled`, when the PaymentIntent was canceled. + @_spi(STP) public let canceledAt: Date? + + /// Capture method of this PaymentIntent + @_spi(STP) public let captureMethod: CaptureMethod + + /// Confirmation method of this PaymentIntent + @_spi(STP) public let confirmationMethod: ConfirmationMethod + + /// When the PaymentIntent was created. + @_spi(STP) public let created: Date + + /// The currency associated with the PaymentIntent. + @_spi(STP) public let currency: String + + /// The `description` field of the PaymentIntent. + /// An arbitrary string attached to the object. Often useful for displaying to users. + @_spi(STP) public let stripeDescription: String? + + /// Whether or not this PaymentIntent was created in livemode. + @_spi(STP) public let livemode: Bool + + /// Email address that the receipt for the resulting payment will be sent to. + @_spi(STP) public let receiptEmail: String? + + /// The Stripe ID of the Source used in this PaymentIntent. + @_spi(STP) public let sourceId: String? + + /// The Stripe ID of the PaymentMethod used in this PaymentIntent. + @_spi(STP) public let paymentMethodId: String? + + /// Status of the PaymentIntent + @_spi(STP) public let status: Status + + /// Status types for a PaymentIntent + @frozen @_spi(STP) public enum Status: String, StripeEnumCodable { + /// Unknown status + case unknown + /// This PaymentIntent requires a PaymentMethod or Source + case requiresPaymentMethod = "requires_payment_method" + /// This PaymentIntent requires a Source + /// Deprecated: Use STPPaymentIntentStatusRequiresPaymentMethod instead. + @available( + *, deprecated, message: "Use STPPaymentIntentStatus.requiresPaymentMethod instead", + renamed: "STPPaymentIntentStatus.requiresPaymentMethod" + ) + case requiresSource = "requires_source" + /// This PaymentIntent needs to be confirmed + case requiresConfirmation = "requires_confirmation" + /// The selected PaymentMethod or Source requires additional authentication steps. + /// Additional actions found via `next_action` + case requiresAction = "requires_action" + /// The selected Source requires additional authentication steps. + /// Additional actions found via `next_source_action` + /// Deprecated: Use STPPaymentIntentStatusRequiresAction instead. + @available( + *, deprecated, message: "Use STPPaymentIntentStatus.requiresAction instead", + renamed: "STPPaymentIntentStatus.requiresAction" + ) + case requiresSourceAction = "requires_source_action" + /// Stripe is processing this PaymentIntent + case processing + /// The payment has succeeded + case succeeded + /// Indicates the payment must be captured, for STPPaymentIntentCaptureMethodManual + case requiresCapture = "requires_capture" + /// This PaymentIntent was canceled and cannot be changed. + case canceled + + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + @frozen @_spi(STP) public enum ConfirmationMethod: String, StripeEnumCodable { + /// Unknown confirmation method + case unknown + /// Confirmed via publishable key + case manual + /// Confirmed via secret key + case automatic + + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + @frozen @_spi(STP) public enum CaptureMethod: String, StripeEnumCodable { + /// Unknown capture method + case unknown + /// The PaymentIntent will be automatically captured + case automatic + /// The PaymentIntent must be manually captured once it has the status + /// `.requiresCapture` + case manual + + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + @_spi(STP) public var _allResponseFieldsStorage: NonEncodableParameters? + } +} + +extension StripeAPI.PaymentIntent { + /// Helper function for extracting PaymentIntent id from the Client Secret. + /// This avoids having to pass around both the id and the secret. + /// - Parameter clientSecret: The `client_secret` from the PaymentIntent + internal static func id(fromClientSecret clientSecret: String) -> String? { + // see parseClientSecret from stripe-js-v3 + let components = clientSecret.components(separatedBy: "_secret_") + if components.count >= 2 && components[0].hasPrefix("pi_") { + return components[0] + } else { + return nil + } + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentIntentParams.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentIntentParams.swift new file mode 100644 index 00000000000..769274e79e5 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentIntentParams.swift @@ -0,0 +1,160 @@ +// +// PaymentIntentParams.swift +// StripeiOS +// +// Created by David Estes on 6/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + @_spi(STP) public struct PaymentIntentParams: StripeEncodable { + /// The client secret of the PaymentIntent. Required + @_spi(STP) public let clientSecret: String + + @_spi(STP) public init(clientSecret: String) { + self.clientSecret = clientSecret + } + + @_spi(STP) public var id: String? { + return PaymentIntent.id(fromClientSecret: clientSecret) + } + + /// Provide a supported `PaymentMethodParams` object, and Stripe will create a + /// PaymentMethod during PaymentIntent confirmation. + /// @note alternative to `paymentMethodId` + @_spi(STP) public var paymentMethodData: PaymentMethodParams? + + /// Provide an already created PaymentMethod's id, and it will be used to confirm the PaymentIntent. + /// @note alternative to `paymentMethodParams` + @_spi(STP) public var paymentMethod: String? + + /// Provide an already created Source's id, and it will be used to confirm the PaymentIntent. + @_spi(STP) public var sourceId: String? + + /// Email address that the receipt for the resulting payment will be sent to. + @_spi(STP) public var receiptEmail: String? + + /// `@YES` to save this PaymentIntent’s PaymentMethod or Source to the associated Customer, + /// if the PaymentMethod/Source is not already attached. + /// This should be a boolean NSNumber, so that it can be `nil` + @_spi(STP) public var savePaymentMethod: Bool? + + /// The URL to redirect your customer back to after they authenticate or cancel + /// their payment on the payment method’s app or site. + /// This should probably be a URL that opens your iOS app. + @_spi(STP) public var returnURL: String? + + /// When provided, this property indicates how you intend to use the payment method that your customer provides after the current payment completes. + /// If applicable, additional authentication may be performed to comply with regional legislation or network rules required to enable the usage of the same payment method for additional payments. + @_spi(STP) public var setupFutureUsage: SetupFutureUsage? + + /// A boolean number to indicate whether you intend to use the Stripe SDK's functionality to handle any PaymentIntent next actions. + /// If set to false, STPPaymentIntent.nextAction will only ever contain a redirect url that can be opened in a webview or mobile browser. + /// When set to true, the nextAction may contain information that the Stripe SDK can use to perform native authentication within your + /// app. + @_spi(STP) public var useStripeSdk: Bool? + + /// Shipping information. + @IncludeUnknownFields + @_spi(STP) public var shipping: ShippingDetails? + + + @_spi(STP) public struct ShippingDetails: StripeEncodable { + /// Generated by by Editor -> Refactor -> Generate Memberwise Initializer + @_spi(STP) public init(address: StripeAPI.PaymentIntentParams.ShippingDetails.Address, name: String, carrier: String? = nil, phone: String? = nil, trackingNumber: String? = nil, _additionalParametersStorage: NonEncodableParameters? = nil) { + self.address = address + self.name = name + self.carrier = carrier + self.phone = phone + self.trackingNumber = trackingNumber + self._additionalParametersStorage = _additionalParametersStorage + } + + /// Shipping address. + @_spi(STP) public var address: Address + + /// Recipient name. + @_spi(STP) public var name: String + + /// The delivery service that shipped a physical product, such as Fedex, UPS, USPS, etc. + @_spi(STP) public var carrier: String? + + /// Recipient phone (including extension). + @_spi(STP) public var phone: String? + + /// The tracking number for a physical product, obtained from the delivery service. If multiple tracking numbers were generated for this purchase, please separate them with commas. + @_spi(STP) public var trackingNumber: String? + + @_spi(STP) public var _additionalParametersStorage: NonEncodableParameters? + + @_spi(STP) public struct Address: StripeEncodable { + /// Generated by by Editor -> Refactor -> Generate Memberwise Initializer + @_spi(STP) public init(city: String? = nil, country: String? = nil, line1: String, line2: String? = nil, postalCode: String? = nil, state: String? = nil, _additionalParametersStorage: NonEncodableParameters? = nil) { + self.city = city + self.country = country + self.line1 = line1 + self.line2 = line2 + self.postalCode = postalCode + self.state = state + self._additionalParametersStorage = _additionalParametersStorage + } + + /// City/District/Suburb/Town/Village. + @_spi(STP) public var city: String? + + /// Two-letter country code (ISO 3166-1 alpha-2). + @_spi(STP) public var country: String? + + /// Address line 1 (Street address/PO Box/Company name). + @_spi(STP) public var line1: String + + /// Address line 2 (Apartment/Suite/Unit/Building). + @_spi(STP) public var line2: String? + + /// ZIP or postal code. + @_spi(STP) public var postalCode: String? + + /// State/County/Province/Region. + @_spi(STP) public var state: String? + + @_spi(STP) public var _additionalParametersStorage: NonEncodableParameters? + } + } + + /// Indicates how you intend to use the payment method that your customer provides after the current payment completes. + /// If applicable, additional authentication may be performed to comply with regional legislation or network rules required to enable the usage of the same payment method for additional payments. + /// - seealso: https://stripe.com/docs/api/payment_intents/object#payment_intent_object-setup_future_usage + @frozen @_spi(STP) public enum SetupFutureUsage: String, StripeEnumCodable { + /// Unknown value. Update your SDK, or use `allResponseFields` for custom handling. + case unknown + /// No value was provided. + case none + /// Indicates you intend to only reuse the payment method when the customer is in your checkout flow. + case onSession + /// Indicates you intend to reuse the payment method when the customer may or may not be in your checkout flow. + case offSession + + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + @_spi(STP) public var _additionalParametersStorage: NonEncodableParameters? + } +} + +extension StripeAPI.PaymentIntentParams { + static internal let isClientSecretValidRegex: NSRegularExpression = try! NSRegularExpression( + pattern: "^pi_[^_]+_secret_[^_]+$", options: []) + + static internal func isClientSecretValid(_ clientSecret: String) -> Bool { + return + (isClientSecretValidRegex.numberOfMatches( + in: clientSecret, + options: .anchored, + range: NSRange(location: 0, length: clientSecret.count))) == 1 + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentMethod.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentMethod.swift new file mode 100644 index 00000000000..bc38e72b4d7 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentMethod.swift @@ -0,0 +1,180 @@ +// +// PaymentMethod.swift +// StripeiOS +// +// Created by David Estes on 6/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + /// PaymentMethod objects represent your customer's payment instruments. They can be used with PaymentIntents to collect payments. + /// - seealso: https://stripe.com/docs/api/payment_methods + public struct PaymentMethod: StripeDecodable { + /// The Stripe ID of the PaymentMethod. + public let id: String + + /// Time at which the object was created. Measured in seconds since the Unix epoch. + public var created: Date? + /// `YES` if the object exists in live mode or the value `NO` if the object exists in test mode. + public var livemode = false + + /// The type of the PaymentMethod. The corresponding, similarly named property contains additional information specific to the PaymentMethod type. + /// e.g. if the type is `Card`, the `card` property is also populated. + public var type: PaymentMethodType? + + /// The type of the PaymentMethod. + @frozen public enum PaymentMethodType: String, StripeEnumCodable { + /// A card payment method. + case card + /// An unknown type. + case unknown + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + /// Billing information associated with the PaymentMethod that may be used or required by particular types of payment methods. + @IncludeUnknownFields + public var billingDetails: BillingDetails? + /// The ID of the Customer to which this PaymentMethod is saved. Nil when the PaymentMethod has not been saved to a Customer. + public var customerId: String? + /// If this is a card PaymentMethod (ie `self.type == .card`), this contains additional details. + @IncludeUnknownFields + public var card: Card? + + public struct Card: StripeDecodable { + public var _allResponseFieldsStorage: NonEncodableParameters? + /// The issuer of the card. + public private(set) var brand: Brand = .unknown + + /// The various card brands to which a payment card can belong. + @frozen public enum Brand: String, StripeEnumCodable { + /// Visa + case visa + /// American Express + case amex + /// Mastercard + case mastercard + /// Discover + case discover + /// JCB + case jcb + /// Diners Club + case diners + /// UnionPay + case unionpay + /// An unknown card brand + case unknown + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + /// Two-letter ISO code representing the country of the card. + public private(set) var country: String? + /// Two-digit number representing the card’s expiration month. + public private(set) var expMonth: Int + /// Four-digit number representing the card’s expiration year. + public private(set) var expYear: Int + /// Card funding type. Can be credit, debit, prepaid, or unknown. + public private(set) var funding: String? + /// The last four digits of the card. + public private(set) var last4: String? + /// Uniquely identifies this particular card number. You can use this attribute to check whether two customers who’ve signed up with you are using the same card number, for example. + public private(set) var fingerprint: String? + + @IncludeUnknownFields + /// Contains information about card networks that can be used to process the payment. + public private(set) var networks: Networks? + + @IncludeUnknownFields + /// Contains details on how this Card maybe be used for 3D Secure authentication. + public private(set) var threeDSecureUsage: ThreeDSecureUsage? + + /// If this Card is part of a Card Wallet, this contains the details of the Card Wallet. + public private(set) var wallet: Wallet? + + public struct Networks: StripeDecodable { + public var _allResponseFieldsStorage: NonEncodableParameters? + + /// All available networks for the card. + public private(set) var available: [String]? + /// The preferred network for the card if one exists. + public private(set) var preferred: String? + } + + /// Contains details on how a `Card` may be used for 3D Secure authentication. + public struct ThreeDSecureUsage: StripeDecodable { + public var _allResponseFieldsStorage: NonEncodableParameters? + + /// `true` if 3D Secure is supported on this card. + public private(set) var supported = false + } + + public struct Wallet: StripeDecodable { + public var _allResponseFieldsStorage: NonEncodableParameters? + /// The type of the Card Wallet. A matching property is populated if the type is `.masterpass` or `.visaCheckout` containing additional information specific to the Card Wallet type. + public private(set) var type: WalletType = .unknown + /// Contains additional Masterpass information, if the type of the Card Wallet is `STPPaymentMethodCardWalletTypeMasterpass` + public private(set) var masterpass: Masterpass? + /// Contains additional Visa Checkout information, if the type of the Card Wallet is `STPPaymentMethodCardWalletTypeVisaCheckout` + public private(set) var visaCheckout: VisaCheckout? + + /// The type of Card Wallet. + @frozen public enum WalletType: String, StripeEnumCodable { + /// Amex Express Checkout + case amexExpressCheckout = "amex_express_checkout" + /// Apple Pay + case applePay = "apple_pay" + /// Google Pay + case googlePay = "google_pay" + /// Masterpass + case masterpass = "masterpass" + /// Samsung Pay + case samsungPay = "samsung_pay" + /// Visa Checkout + case visaCheckout = "visa_checkout" + /// An unknown Card Wallet type. + case unknown = "unknown" + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + public struct Masterpass: StripeDecodable { + public var _allResponseFieldsStorage: NonEncodableParameters? + + /// Owner’s verified email. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var email: String? + /// Owner’s verified email. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var name: String? + /// Owner’s verified billing address. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var billingAddress: BillingDetails.Address? + /// Owner’s verified shipping address. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var shippingAddress: BillingDetails.Address? + } + + /// A Visa Checkout Card Wallet + /// - seealso: https://stripe.com/docs/visa-checkout + public struct VisaCheckout: StripeDecodable { + /// Owner’s verified email. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var email: String? + /// Owner’s verified email. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var name: String? + /// Owner’s verified billing address. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var billingAddress: BillingDetails.Address? + /// Owner’s verified shipping address. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var shippingAddress: BillingDetails.Address? + + public var _allResponseFieldsStorage: NonEncodableParameters? + } + } + } + + public var _allResponseFieldsStorage: NonEncodableParameters? + } +} + diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentMethodParams.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentMethodParams.swift new file mode 100644 index 00000000000..0d29c77687a --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentMethodParams.swift @@ -0,0 +1,72 @@ +// +// PaymentMethodParams.swift +// StripeiOS +// +// Created by David Estes on 6/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + /// An object representing parameters used to create a PaymentMethod object. + /// - seealso: https://stripe.com/docs/api/payment_methods/create + @_spi(STP) public struct PaymentMethodParams: StripeEncodable { + /// The type of payment method. + /// The associated property will contain additional information (e.g. `type == .card` means `card` should also be populated). + @_spi(STP) public var type: PaymentMethod.PaymentMethodType + + /// If this is a card PaymentMethod, this contains the user’s card details. + @IncludeUnknownFields + @_spi(STP) public var card: Card? + + /// Billing information associated with the PaymentMethod that may be used or required by particular types of payment methods. + @IncludeUnknownFields + @_spi(STP) public var billingDetails: BillingDetails? + + /// Used internally to identify the version of the SDK sending the request + @_spi(STP) public var paymentUserAgent: String? = { + return STPAPIClient.paymentUserAgent + }() + + @_spi(STP) public struct Card: StripeEncodable { + /// The card number, as a string without any separators. Ex. "4242424242424242" + @_spi(STP) public var number: String? + /// Number representing the card's expiration month. Ex. 1 + @_spi(STP) public var expMonth: Int? + /// Two- or four-digit number representing the card's expiration year. + @_spi(STP) public var expYear: Int? + /// For backwards compatibility, you can alternatively set this as a Stripe token (e.g., for Apple Pay) + @_spi(STP) public var token: String? + /// Card security code. It is highly recommended to always include this value. + @_spi(STP) public var cvc: String? + + /// The last 4 digits of the card's number, if it's been set, otherwise nil. + @_spi(STP) public var last4: String? { + if number != nil && (number?.count ?? 0) >= 4 { + return (number as NSString?)?.substring(from: (number?.count ?? 0) - 4) + } else { + return nil + } + } + @_spi(STP) public var _additionalParametersStorage: NonEncodableParameters? + } + + @_spi(STP) public var _additionalParametersStorage: NonEncodableParameters? + } +} + +extension StripeAPI.PaymentMethodParams.Card: CustomStringConvertible, CustomDebugStringConvertible, CustomLeafReflectable { + @_spi(STP) public var debugDescription: String { + return description + } + + @_spi(STP) public var description: String { + return "Card \(last4 ?? "")" + } + + @_spi(STP) public var customMirror: Mirror { + return Mirror(reflecting:self.description) + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/SetupIntent.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/SetupIntent.swift new file mode 100644 index 00000000000..37d02a42c8b --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/SetupIntent.swift @@ -0,0 +1,58 @@ +// +// SetupIntent.swift +// StripeiOS +// +// Created by David Estes on 6/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + @_spi(STP) public struct SetupIntent: StripeDecodable { + @_spi(STP) public let id: String + // TODO: (MOBILESDK-467) Add modern bindings for more SetupIntent fields + @_spi(STP) public let status: SetupIntentStatus? + + /// Status types for an STPSetupIntent + @frozen @_spi(STP) public enum SetupIntentStatus: String, StripeEnumCodable { + /// Unknown status + case unknown + /// This SetupIntent requires a PaymentMethod + case requiresPaymentMethod = "requires_payment_method" + /// This SetupIntent needs to be confirmed + case requiresConfirmation = "requires_confirmation" + /// The selected PaymentMethod requires additional authentication steps. + /// Additional actions found via the `nextAction` property of `STPSetupIntent` + case requiresAction = "requires_action" + /// Stripe is processing this SetupIntent + case processing + /// The SetupIntent has succeeded + case succeeded + /// This SetupIntent was canceled and cannot be changed. + case canceled + + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + @_spi(STP) public var _allResponseFieldsStorage: NonEncodableParameters? + } +} + +extension StripeAPI.SetupIntent { + /// Helper function for extracting SetupIntent id from the Client Secret. + /// This avoids having to pass around both the id and the secret. + /// - Parameter clientSecret: The `client_secret` from the SetupIntent + internal static func id(fromClientSecret clientSecret: String) -> String? { + // see parseClientSecret from stripe-js-v3 + let components = clientSecret.components(separatedBy: "_secret_") + if components.count >= 2 && components[0].hasPrefix("seti_") { + return components[0] + } else { + return nil + } + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/SetupIntentParams.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/SetupIntentParams.swift new file mode 100644 index 00000000000..5a73572df71 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/SetupIntentParams.swift @@ -0,0 +1,56 @@ +// +// SetupIntentParams.swift +// StripeiOS +// +// Created by David Estes on 6/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + @_spi(STP) public struct SetupIntentConfirmParams: StripeEncodable { + /// Generated by by Editor -> Refactor -> Generate Memberwise Initializer + @_spi(STP) public init(clientSecret: String, paymentMethodParams: StripeAPI.PaymentMethodParams? = nil, paymentMethod: String? = nil, returnURL: String? = nil, useStripeSdk: Bool? = nil, _additionalParametersStorage: NonEncodableParameters? = nil) { + self.clientSecret = clientSecret + self.paymentMethodParams = paymentMethodParams + self.paymentMethod = paymentMethod + self.returnURL = returnURL + self.useStripeSdk = useStripeSdk + self._additionalParametersStorage = _additionalParametersStorage + } + + /// The client secret of the SetupIntent. Required. + @_spi(STP) public let clientSecret: String + /// Provide a supported `PaymentMethodParams` object, and Stripe will create a + /// PaymentMethod during PaymentIntent confirmation. + /// @note alternative to `paymentMethodId` + @_spi(STP) public var paymentMethodParams: PaymentMethodParams? + /// Provide an already created PaymentMethod's id, and it will be used to confirm the SetupIntent. + /// @note alternative to `paymentMethodParams` + @_spi(STP) public var paymentMethod: String? + /// The URL to redirect your customer back to after they authenticate or cancel + /// their payment on the payment method’s app or site. + /// This should probably be a URL that opens your iOS app. + @_spi(STP) public var returnURL: String? + /// A boolean number to indicate whether you intend to use the Stripe SDK's functionality to handle any SetupIntent next actions. + /// If set to false, SetupIntent.nextAction will only ever contain a redirect url that can be opened in a webview or mobile browser. + /// When set to true, the nextAction may contain information that the Stripe SDK can use to perform native authentication within your + /// app. + @_spi(STP) public var useStripeSdk: Bool? + + @_spi(STP) public var _additionalParametersStorage: NonEncodableParameters? + + // MARK: - Utilities + static private let regex = try! NSRegularExpression( + pattern: "^seti_[^_]+_secret_[^_]+$", options: []) + @_spi(STP) public static func isClientSecretValid(_ clientSecret: String) -> Bool { + return + (regex.numberOfMatches( + in: clientSecret, + options: .anchored, + range: NSRange(location: 0, length: clientSecret.count))) == 1 + } + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/Token.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/Token.swift new file mode 100644 index 00000000000..549e4e41a3d --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/Token.swift @@ -0,0 +1,147 @@ +// +// Token.swift +// StripeiOS +// +// Created by David Estes on 7/14/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit +@_spi(STP) import StripeCore + +extension StripeAPI { + struct Token: StripeDecodable { + var _allResponseFieldsStorage: NonEncodableParameters? + + /// The value of the token. You can store this value on your server and use it to make charges and customers. + /// - seealso: https://stripe.com/docs/charges + let id: String + /// Whether or not this token was created in livemode. Will be YES if you used your Live Publishable Key, and NO if you used your Test Publishable Key. + var livemode: Bool + /// The type of this token. + var type: TokenType + + /// Possible Token types + enum TokenType: String, Decodable { + /// Account token type + case account + /// Bank account token type + case bankAccount = "bank_account" + /// Card token type + case card + /// PII token type + case PII = "pii" + /// CVC update token type + case cvcUpdate = "cvc_update" + } + + /// The credit card details that were used to create the token. Will only be set if the token was created via a credit card or Apple Pay, otherwise it will be + /// nil. + @IncludeUnknownFields + var card: Card? + // /// The bank account details that were used to create the token. Will only be set if the token was created with a bank account, otherwise it will be nil. + // Not yet implemented. + // var bankAccount: BankAccount? + /// When the token was created. + var created: Date? + + struct Card: StripeDecodable { + var _allResponseFieldsStorage: NonEncodableParameters? + + /// The last 4 digits of the card. + var last4: String + /// For cards made with Apple Pay, this refers to the last 4 digits of the + /// "Device Account Number" for the tokenized card. For regular cards, it will + /// be nil. + var dynamicLast4: String? + /// Whether or not the card originated from Apple Pay. + var isApplePayCard: Bool { + return (allResponseFields["tokenization_method"] as? String) == "apple_pay" + } + /// The card's expiration month. 1-indexed (i.e. 1 == January) + var expMonth: Int + /// The card's expiration year. + var expYear: Int + /// The cardholder's name. + var name: String? + + /// City/District/Suburb/Town/Village. + var addressCity: String? + + /// Billing address country, if provided when creating card. + var addressCountry: String? + + /// Address line 1 (Street address/PO Box/Company name). + var addressLine1: String? + + /// If address_line1 was provided, results of the check. + var addressLine1Check: AddressCheck? + + /// Results of an address check. + enum AddressCheck: String, Decodable { + case pass + case fail + case unavailable + case unchecked + } + + /// Address line 2 (Apartment/Suite/Unit/Building). + var addressLine2: String? + + /// State/County/Province/Region. + var addressState: String? + + /// ZIP or postal code. + var addressZip: String? + + /// If address_zip was provided, results of the check. + var addressZipCheck: AddressCheck? + + /// The issuer of the card. + var brand: Brand = .unknown + + /// The various card brands to which a payment card can belong. + enum Brand: String, Decodable { + /// Visa card + case visa = "Visa" + /// American Express card + case amex = "American Express" + /// Mastercard card + case mastercard = "MasterCard" + /// Discover card + case discover = "Discover" + /// JCB card + case JCB = "JCB" + /// Diners Club card + case dinersClub = "Diners Club" + /// UnionPay card + case unionPay = "UnionPay" + /// An unknown card brand type + case unknown = "Unknown" + } + + /// The funding source for the card (credit, debit, prepaid, or other) + var funding: FundingType = .other + + /// The various funding sources for a payment card. + enum FundingType: String, Decodable { + /// Debit card funding + case debit + /// Credit card funding + case credit + /// Prepaid card funding + case prepaid + /// An other or unknown type of funding source. + case other + } + + /// Two-letter ISO code representing the issuing country of the card. + var country: String? + /// This is only applicable when tokenizing debit cards to issue payouts to managed + /// accounts. You should not set it otherwise. The card can then be used as a + /// transfer destination for funds in this currency. + var currency: String? + } + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/PaymentIntent+API.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/PaymentIntent+API.swift new file mode 100644 index 00000000000..1293794c69d --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/PaymentIntent+API.swift @@ -0,0 +1,64 @@ +// +// PaymentIntent+API.swift +// StripeiOS +// +// Created by David Estes on 8/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI.PaymentIntent { + /// A callback to be run with a PaymentIntent response from the Stripe API. + /// - Parameters: + /// - paymentIntent: The Stripe PaymentIntent from the response. Will be nil if an error occurs. - seealso: PaymentIntent + /// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. + @_spi(STP) public typealias PaymentIntentCompletionBlock = (Result) -> Void + + /// Retrieves the PaymentIntent object using the given secret. - seealso: https://stripe.com/docs/api#retrieve_payment_intent + /// - Parameters: + /// - secret: The client secret of the payment intent to be retrieved. Cannot be nil. + /// - completion: The callback to run with the returned PaymentIntent object, or an error. + @_spi(STP) public static func get( + apiClient: STPAPIClient = .shared, + clientSecret: String, + completion: @escaping PaymentIntentCompletionBlock + ) { + assert( + StripeAPI.PaymentIntentParams.isClientSecretValid(clientSecret), + "`secret` format does not match expected client secret formatting.") + guard let identifier = StripeAPI.PaymentIntent.id(fromClientSecret: clientSecret) else { + completion(.failure(StripeError.invalidRequest)) + return + } + let endpoint = "\(Resource)/\(identifier)" + let parameters: [String: String] = ["client_secret": clientSecret] + + apiClient.get(resource: endpoint, parameters: parameters, completion: completion) + } + + /// Confirms the PaymentIntent object with the provided params object. + /// At a minimum, the params object must include the `clientSecret`. + /// - seealso: https://stripe.com/docs/api#confirm_payment_intent + /// @note Use the `confirmPayment:withAuthenticationContext:completion:` method on `PaymentHandler` instead + /// of calling this method directly. It handles any authentication necessary for you. - seealso: https://stripe.com/docs/mobile/ios/authentication + /// - Parameters: + /// - paymentIntentParams: The `PaymentIntentParams` to pass to `/confirm` + /// - completion: The callback to run with the returned PaymentIntent object, or an error. + @_spi(STP) public static func confirm( + apiClient: STPAPIClient = .shared, + params: StripeAPI.PaymentIntentParams, + completion: @escaping PaymentIntentCompletionBlock + ) { + guard let identifier = StripeAPI.PaymentIntent.id(fromClientSecret: params.clientSecret) else { + completion(.failure(StripeError.invalidRequest)) + return + } + let endpoint = "\(Resource)/\(identifier)/confirm" + + apiClient.post(resource: endpoint, object: params, completion: completion) + } + + static let Resource = "payment_intents" +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/PaymentMethod+API.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/PaymentMethod+API.swift new file mode 100644 index 00000000000..0d1b712c2d7 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/PaymentMethod+API.swift @@ -0,0 +1,57 @@ +// +// PaymentMethod+API.swift +// StripeiOS +// +// Created by David Estes on 8/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit +@_spi(STP) import StripeCore + +extension StripeAPI.PaymentMethod { + /// A callback to be run with a PaymentMethod response from the Stripe API. + /// - Parameters: + /// - paymentMethod: The Stripe PaymentMethod from the response. Will be nil if an error occurs. - seealso: PaymentMethod + /// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. + @_spi(STP) public typealias PaymentMethodCompletionBlock = (Result) -> Void + + static func create( + apiClient: STPAPIClient = .shared, + params: StripeAPI.PaymentMethodParams, + completion: @escaping PaymentMethodCompletionBlock + ) { + STPAnalyticsClient.sharedClient.logPaymentMethodCreationAttempt(paymentMethodType: params.type.rawValue) + apiClient.post(resource: Resource, object: params, completion: completion) + } + + /// Converts a PKPayment object into a Stripe Payment Method using the Stripe API. + /// - Parameters: + /// - payment: The user's encrypted payment information as returned from a PKPaymentAuthorizationController. Cannot be nil. + /// - completion: The callback to run with the returned Stripe source (and any errors that may have occurred). + @_spi(STP) public static func create( + apiClient: STPAPIClient = .shared, + payment: PKPayment, + completion: @escaping PaymentMethodCompletionBlock + ) { + StripeAPI.Token.create(apiClient: apiClient, payment: payment) { (result) in + guard let token = try? result.get() else { + if case .failure(let error) = result { + completion(.failure(error)) + } else { + completion(.failure(NSError.stp_genericConnectionError())) + } + return + } + var cardParams = StripeAPI.PaymentMethodParams.Card() + cardParams.token = token.id + let billingDetails = StripeAPI.BillingDetails(from: payment) + var paymentMethodParams = StripeAPI.PaymentMethodParams(type: .card, card: cardParams) + paymentMethodParams.billingDetails = billingDetails + Self.create(apiClient: apiClient, params: paymentMethodParams, completion: completion) + } + } + + static let Resource = "payment_methods" +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/SetupIntent+API.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/SetupIntent+API.swift new file mode 100644 index 00000000000..cf8ab742450 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/SetupIntent+API.swift @@ -0,0 +1,64 @@ +// +// SetupIntent+API.swift +// StripeiOS +// +// Created by David Estes on 8/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI.SetupIntent { + /// A callback to be run with a SetupIntent response from the Stripe API. + /// - Parameters: + /// - setupIntent: The Stripe SetupIntent from the response. Will be nil if an error occurs. - seealso: SetupIntent + /// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. + @_spi(STP) public typealias SetupIntentCompletionBlock = (Result) -> Void + + /// Retrieves the SetupIntent object using the given secret. - seealso: https://stripe.com/docs/api/setup_intents/retrieve + /// - Parameters: + /// - secret: The client secret of the SetupIntent to be retrieved. Cannot be nil. + /// - completion: The callback to run with the returned SetupIntent object, or an error. + @_spi(STP) public static func get( + apiClient: STPAPIClient = .shared, + clientSecret: String, + completion: @escaping SetupIntentCompletionBlock + ) { + assert( + StripeAPI.SetupIntentConfirmParams.isClientSecretValid(clientSecret), + "`secret` format does not match expected client secret formatting.") + guard let identifier = StripeAPI.SetupIntent.id(fromClientSecret: clientSecret) else { + completion(.failure(StripeError.invalidRequest)) + return + } + let endpoint = "\(Resource)/\(identifier)" + let parameters: [String: String] = ["client_secret": clientSecret] + + apiClient.get(resource: endpoint, parameters: parameters, completion: completion) + } + + /// Confirms the SetupIntent object with the provided params object. + /// At a minimum, the params object must include the `clientSecret`. + /// - seealso: https://stripe.com/docs/api/setup_intents/confirm + /// @note Use the `confirmSetupIntent:withAuthenticationContext:completion:` method on `PaymentHandler` instead + /// of calling this method directly. It handles any authentication necessary for you. - seealso: https://stripe.com/docs/mobile/ios/authentication + /// - Parameters: + /// - setupIntentParams: The `SetupIntentConfirmParams` to pass to `/confirm` + /// - completion: The callback to run with the returned PaymentIntent object, or an error. + @_spi(STP) public static func confirm( + apiClient: STPAPIClient = .shared, + params: StripeAPI.SetupIntentConfirmParams, + completion: @escaping SetupIntentCompletionBlock + ) { + guard let identifier = StripeAPI.SetupIntent.id(fromClientSecret: params.clientSecret) else { + completion(.failure(StripeError.invalidRequest)) + return + } + let endpoint = "\(Resource)/\(identifier)/confirm" + + apiClient.post(resource: endpoint, object: params, completion: completion) + } + + static let Resource = "setup_intents" +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Token+API.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Token+API.swift new file mode 100644 index 00000000000..0f6f578c2f3 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Token+API.swift @@ -0,0 +1,88 @@ +// +// Token+API.swift +// StripeiOS +// +// Created by David Estes on 8/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit +@_spi(STP) import StripeCore + +extension StripeAPI.Token { + /// A callback to be run with a token response from the Stripe API. + /// - Parameters: + /// - token: The Stripe token from the response. Will be nil if an error occurs. - seealso: STPToken + /// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. + typealias TokenCompletionBlock = (Result) -> Void + + /// Converts a PKPayment object into a Stripe token using the Stripe API. + /// - Parameters: + /// - payment: The user's encrypted payment information as returned from a PKPaymentAuthorizationController. Cannot be nil. + /// - completion: The callback to run with the returned Stripe token (and any errors that may have occurred). + static func create(apiClient: STPAPIClient = .shared, + payment: PKPayment, + completion: @escaping TokenCompletionBlock) + { + let params = payment.stp_tokenParameters(apiClient: apiClient) + create( + apiClient: apiClient, + parameters: params, + completion: completion) + } + + static func create(apiClient: STPAPIClient = .shared, + parameters: [String: Any], + completion: @escaping TokenCompletionBlock + ) { + let tokenType = STPAnalyticsClient.tokenType(fromParameters: parameters) + var mutableParams = parameters + STPTelemetryClient.shared.addTelemetryFields(toParams: &mutableParams) + mutableParams = STPAPIClient.paramsAddingPaymentUserAgent(mutableParams) + STPAnalyticsClient.sharedClient.logTokenCreationAttempt(tokenType: tokenType) + apiClient.post(resource: Resource, parameters: mutableParams, completion: completion) + STPTelemetryClient.shared.sendTelemetryData() + } + + static let Resource = "tokens" +} + +@_spi(STP) public extension PKPayment { + func stp_tokenParameters(apiClient: STPAPIClient) -> [String: Any] { + let paymentString = String(data: self.token.paymentData, encoding: .utf8) + var payload: [String: Any] = [:] + payload["pk_token"] = paymentString + if let billingContact = self.billingContact { + payload["card"] = billingContact.addressParams + } + + assert( + !((paymentString?.count ?? 0) == 0 + && apiClient.publishableKey?.hasPrefix("pk_live") ?? false), + "The pk_token is empty. Using Apple Pay with an iOS Simulator while not in Stripe Test Mode will always fail." + ) + + let paymentInstrumentName = self.token.paymentMethod.displayName + if let paymentInstrumentName = paymentInstrumentName { + payload["pk_token_instrument_name"] = paymentInstrumentName + } + + let paymentNetwork = self.token.paymentMethod.network + if let paymentNetwork = paymentNetwork { + // Note: As of SDK 20.0.0, this will return `PKPaymentNetwork(_rawValue: MasterCard)`. + // We're intentionally leaving it this way: See RUN_MOBILESDK-125. + payload["pk_token_payment_network"] = paymentNetwork + } + + var transactionIdentifier = self.token.transactionIdentifier + if transactionIdentifier != "" { + if self.stp_isSimulated() { + transactionIdentifier = PKPayment.stp_testTransactionIdentifier() ?? "" + } + payload["pk_token_transaction_id"] = transactionIdentifier + } + + return payload + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/Analytics/STPAnalyticsClient+Payments.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Analytics/STPAnalyticsClient+Payments.swift new file mode 100644 index 00000000000..a146f190d94 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Analytics/STPAnalyticsClient+Payments.swift @@ -0,0 +1,55 @@ +// +// STPAnalyticsClient+Payments.swift +// StripeApplePay +// +// Created by David Estes on 1/24/22. +// + +import Foundation +@_spi(STP) import StripeCore + +/** + An analytic specific to payments that serializes payment-specific + information into its params. + */ +@_spi(STP) public protocol PaymentAnalytic: Analytic { + var productUsage: Set { get } + var additionalParams: [String: Any] { get } +} + +@_spi(STP) public extension PaymentAnalytic { + var params: [String: Any] { + var params = additionalParams + + params["apple_pay_enabled"] = NSNumber(value: StripeAPI.deviceSupportsApplePay()) + params["ocr_type"] = STPAnalyticsClient.ocrTypeString() + params["pay_var"] = STPAnalyticsClient.paymentsSDKVariant + return params + } +} + +extension STPAnalyticsClient { + @_spi(STP) public class func ocrTypeString() -> String { + if #available(iOS 13.0, macCatalyst 14.0, *) { + // "STPCardScanner" is STPCardScanner.stp_analyticsIdentifier, but STPCardScanner only exists in Stripe.framework. + if STPAnalyticsClient.sharedClient.productUsage.contains( + "STPCardScanner") + { + return "stripe" + } + } + return "none" + } + + @_spi(STP) public static let paymentsSDKVariant: String = { + if NSClassFromString("STPPaymentContext") != nil { + // This is the full legacy Payments SDK, including Basic Integration. + return "legacy" + } + + // TODO (MOBILESDK-593): Add a value for the PaymentSheet-only SDK. + + // This is the Apple Pay-only SDK. + return "applepay" + }() +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/Analytics/STPAnalyticsClient+PaymentsAPI.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Analytics/STPAnalyticsClient+PaymentsAPI.swift new file mode 100644 index 00000000000..31035ccb433 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Analytics/STPAnalyticsClient+PaymentsAPI.swift @@ -0,0 +1,39 @@ +// +// STPAnalyticsClient+ApplePay.swift +// StripeApplePay +// +// Created by David Estes on 1/21/22. +// + +import Foundation +@_spi(STP) import StripeCore + +extension STPAnalyticsClient { + // MARK: - Log events + + func logPaymentMethodCreationAttempt(paymentMethodType: String?) { + log(analytic: PaymentAPIAnalytic( + event: .paymentMethodCreation, + productUsage: productUsage, + additionalParams: [ + "source_type": paymentMethodType ?? "unknown" + ] + )) + } + + func logTokenCreationAttempt(tokenType: String?) { + log(analytic: PaymentAPIAnalytic( + event: .tokenCreation, + productUsage: productUsage, + additionalParams: [ + "token_type": tokenType ?? "unknown" + ] + )) + } +} + +struct PaymentAPIAnalytic: PaymentAnalytic { + let event: STPAnalyticEvent + let productUsage: Set + let additionalParams: [String : Any] +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/Categories/STPAPIClient+PaymentsCore.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Categories/STPAPIClient+PaymentsCore.swift new file mode 100644 index 00000000000..cdf316a6b76 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Categories/STPAPIClient+PaymentsCore.swift @@ -0,0 +1,25 @@ +// +// STPAPIClient+PaymentsCore.swift +// StripeApplePay +// +// Created by David Estes on 1/25/22. +// + +import Foundation +@_spi(STP) import StripeCore + +extension STPAPIClient { + @_spi(STP) public static var paymentUserAgent: String { + var paymentUserAgent = "stripe-ios/\(STPAPIClient.STPSDKVersion)" + let variant = "variant.\(STPAnalyticsClient.paymentsSDKVariant)" + let components = [paymentUserAgent, variant] + STPAnalyticsClient.sharedClient.productUsage + paymentUserAgent = components.joined(separator: "; ") + return paymentUserAgent + } + + @_spi(STP) public class func paramsAddingPaymentUserAgent(_ params: [String: Any]) -> [String: Any] { + var newParams = params + newParams["payment_user_agent"] = Self.paymentUserAgent + return newParams + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/Categories/UserDefaults+PaymentsCore.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Categories/UserDefaults+PaymentsCore.swift new file mode 100644 index 00000000000..d7afb85cd6d --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Categories/UserDefaults+PaymentsCore.swift @@ -0,0 +1,44 @@ +// +// UserDefaults+PaymentsCore.swift +// StripeApplePay +// +// Created by David Estes on 11/16/21. +// + +import Foundation + +extension UserDefaults { + /// Canonical list of all UserDefaults keys the SDK uses + enum StripePaymentsCoreKeys: String { + /// The key for a dictionary FraudDetectionData dictionary + case fraudDetectionData = "com.stripe.lib:FraudDetectionDataKey" + } + + var fraudDetectionData: FraudDetectionData? { + get { + let key = StripePaymentsCoreKeys.fraudDetectionData.rawValue + guard let data = data(forKey: key) else { + return nil + } + do { + return try JSONDecoder().decode(FraudDetectionData.self, from: data) + } + catch(let e) { + assertionFailure("\(e)") + return nil + } + } + set { + let key = StripePaymentsCoreKeys.fraudDetectionData.rawValue + do { + let data = try JSONEncoder().encode(newValue) + setValue(data, forKey: key) + } + catch(let e) { + assertionFailure("\(e)") + return + } + } + } +} + diff --git a/Stripe/FraudDetectionData.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Telemetry/FraudDetectionData.swift similarity index 84% rename from Stripe/FraudDetectionData.swift rename to StripeApplePay/StripeApplePay/Source/PaymentsCore/Telemetry/FraudDetectionData.swift index c7997352cdf..73c493afc97 100644 --- a/Stripe/FraudDetectionData.swift +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Telemetry/FraudDetectionData.swift @@ -13,14 +13,14 @@ fileprivate let SIDLifetime: TimeInterval = 30 * 60 // 30 minutes /// Contains encoded values returned from m.stripe.com /// - Note: See `STPTelemetryClient`. /// - Note: See `StripeAPI.advancedFraudSignalsEnabled` -final class FraudDetectionData: Codable { - static let shared: FraudDetectionData = +@_spi(STP) public final class FraudDetectionData: Codable { + @_spi(STP) public static let shared: FraudDetectionData = // Load initial value from UserDefaults UserDefaults.standard.fraudDetectionData ?? FraudDetectionData() - var muid: String? - var guid: String? - var sid: String? + @_spi(STP) public var muid: String? + @_spi(STP) public var guid: String? + @_spi(STP) public var sid: String? /// The approximate time that the sid was generated from m.stripe.com /// Intended to be used to expire the sid after `SIDLifetime` seconds @@ -63,7 +63,7 @@ final class FraudDetectionData: Codable { } extension FraudDetectionData: Equatable { - static func == (lhs: FraudDetectionData, rhs: FraudDetectionData) -> Bool { + @_spi(STP) public static func == (lhs: FraudDetectionData, rhs: FraudDetectionData) -> Bool { return lhs.muid == rhs.muid && lhs.sid == rhs.sid && diff --git a/Stripe/STPTelemetryClient.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Telemetry/STPTelemetryClient.swift similarity index 91% rename from Stripe/STPTelemetryClient.swift rename to StripeApplePay/StripeApplePay/Source/PaymentsCore/Telemetry/STPTelemetryClient.swift index de40b58546c..0f16e89e5b2 100644 --- a/Stripe/STPTelemetryClient.swift +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Telemetry/STPTelemetryClient.swift @@ -12,18 +12,18 @@ import UIKit private let TelemetryURL = URL(string: "https://m.stripe.com/6")! -final class STPTelemetryClient: NSObject { - @objc(sharedInstance) static var shared: STPTelemetryClient = STPTelemetryClient( +@_spi(STP) public final class STPTelemetryClient: NSObject { + @_spi(STP) public static var shared: STPTelemetryClient = STPTelemetryClient( sessionConfiguration: StripeAPIConfiguration.sharedUrlSessionConfiguration) - func addTelemetryFields(toParams params: inout [String: Any]) { + @_spi(STP) public func addTelemetryFields(toParams params: inout [String: Any]) { params["muid"] = fraudDetectionData.muid params["guid"] = fraudDetectionData.guid fraudDetectionData.resetSIDIfExpired() params["sid"] = fraudDetectionData.sid } - @objc func paramsByAddingTelemetryFields(toParams params: [String: Any]) -> [String: Any] { + @_spi(STP) public func paramsByAddingTelemetryFields(toParams params: [String: Any]) -> [String: Any] { var mutableParams = params mutableParams["muid"] = fraudDetectionData.muid mutableParams["guid"] = fraudDetectionData.guid @@ -37,7 +37,7 @@ final class STPTelemetryClient: NSObject { - Parameter forceSend: ⚠️ Always send the request. Only pass this for testing purposes. - Parameter completion: Called with the result of the telemetry network request. */ - func sendTelemetryData( + @_spi(STP) public func sendTelemetryData( forceSend: Bool = false, completion: ((Result<[String: Any], Error>) -> ())? = nil ) { @@ -48,7 +48,7 @@ final class STPTelemetryClient: NSObject { sendTelemetryRequest(jsonPayload: payload, completion: completion) } - func updateFraudDetectionIfNecessary(completion: @escaping ((Result) -> ())) { + @_spi(STP) public func updateFraudDetectionIfNecessary(completion: @escaping ((Result) -> ())) { fraudDetectionData.resetSIDIfExpired() if fraudDetectionData.muid == nil || fraudDetectionData.sid == nil { sendTelemetryRequest( @@ -71,7 +71,7 @@ final class STPTelemetryClient: NSObject { private let urlSession: URLSession - class func shouldSendTelemetry() -> Bool { + @_spi(STP) public class func shouldSendTelemetry() -> Bool { #if targetEnvironment(simulator) return false #else @@ -79,7 +79,7 @@ final class STPTelemetryClient: NSObject { #endif } - init(sessionConfiguration config: URLSessionConfiguration) { + @_spi(STP) public init(sessionConfiguration config: URLSessionConfiguration) { urlSession = URLSession(configuration: config) super.init() } diff --git a/StripeApplePay/StripeApplePay/Source/Placeholder.swift b/StripeApplePay/StripeApplePay/Source/Placeholder.swift deleted file mode 100644 index 30771651c6f..00000000000 --- a/StripeApplePay/StripeApplePay/Source/Placeholder.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Placeholder.swift -// StripeApplePay -// -// Created by David Estes on 11/8/21. -// - -import Foundation - -/* - TODO(davidestes): - This is a dummy placeholder swift file to make `pod lib lint` validate. - It will be deleted when we migrate code from `Stripe` -> `StripeApplePay`. -*/ diff --git a/Tests/Tests/STPTelemetryClientFunctionalTest.swift b/StripeApplePay/StripeApplePayTests/PaymentsCore/STPTelemetryClientFunctionalTest.swift similarity index 98% rename from Tests/Tests/STPTelemetryClientFunctionalTest.swift rename to StripeApplePay/StripeApplePayTests/PaymentsCore/STPTelemetryClientFunctionalTest.swift index 6f534620d32..54684b09779 100644 --- a/Tests/Tests/STPTelemetryClientFunctionalTest.swift +++ b/StripeApplePay/StripeApplePayTests/PaymentsCore/STPTelemetryClientFunctionalTest.swift @@ -7,7 +7,7 @@ // import XCTest -@testable import Stripe +@testable @_spi(STP) import StripeApplePay class STPTelemetryClientFunctionalTest: XCTestCase { func testSendFraudDetectionData() { diff --git a/Tests/Tests/STPTelemetryClientTest.swift b/StripeApplePay/StripeApplePayTests/PaymentsCore/STPTelemetryClientTest.swift similarity index 98% rename from Tests/Tests/STPTelemetryClientTest.swift rename to StripeApplePay/StripeApplePayTests/PaymentsCore/STPTelemetryClientTest.swift index a8f6710bdfd..a9b4459e3b7 100644 --- a/Tests/Tests/STPTelemetryClientTest.swift +++ b/StripeApplePay/StripeApplePayTests/PaymentsCore/STPTelemetryClientTest.swift @@ -8,7 +8,7 @@ import XCTest -@testable import Stripe +@testable @_spi(STP) import StripeApplePay class STPTelemetryClientTest: XCTestCase { diff --git a/StripeApplePay/StripeApplePayTests/STPAnalyticsClient+ApplePayTest.swift b/StripeApplePay/StripeApplePayTests/STPAnalyticsClient+ApplePayTest.swift new file mode 100644 index 00000000000..ebce17dba62 --- /dev/null +++ b/StripeApplePay/StripeApplePayTests/STPAnalyticsClient+ApplePayTest.swift @@ -0,0 +1,25 @@ +// +// STPAnalyticsClient+ApplePayTest.swift +// StripeApplePayTests +// +// Created by David Estes on 2/3/22. +// + +import Foundation +import XCTest +@_spi(STP) @testable import StripeApplePay +@_spi(STP) @testable import StripeCore + +class STPAnalyticsClientApplePayTest: XCTestCase { + func testApplePaySDKVariantPayload() throws { + // setup + let analytic = PaymentAPIAnalytic( + event: .paymentMethodCreation, + productUsage: [], + additionalParams: [:] + ) + let client = STPAnalyticsClient() + let payload = client.payload(from: analytic) + XCTAssertEqual("applepay", payload["pay_var"] as? String) + } +} diff --git a/StripeApplePay/StripeApplePayTests/STPPaymentMethodFunctionalTest.swift b/StripeApplePay/StripeApplePayTests/STPPaymentMethodFunctionalTest.swift new file mode 100644 index 00000000000..43d411ce623 --- /dev/null +++ b/StripeApplePay/StripeApplePayTests/STPPaymentMethodFunctionalTest.swift @@ -0,0 +1,94 @@ +// +// STPPaymentMethodFunctionalTest.swift +// StripeiOS Tests +// +// Created by David Estes on 8/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import XCTest +@_spi(STP) import StripeCore // for StripeError +@_spi(STP) @testable import StripeApplePay + +let STPTestingDefaultPublishableKey = "pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6" +public let STPTestingNetworkRequestTimeout: TimeInterval = 8 + +class STPPaymentMethodModernTest: XCTestCase { + func testCreateCardPaymentMethod() { + let expectation = self.expectation(description: "Created") + let apiClient = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + var params = StripeAPI.PaymentMethodParams(type: .card) + var card = StripeAPI.PaymentMethodParams.Card() + card.number = "4242424242424242" + card.expYear = 28 + card.expMonth = 12 + card.cvc = "100" + var billingAddress = StripeAPI.BillingDetails.Address() + billingAddress.city = "San Francisco" + billingAddress.country = "US" + billingAddress.line1 = "150 Townsend St" + billingAddress.line2 = "4th Floor" + billingAddress.postalCode = "94103" + billingAddress.state = "CA" + + var billingDetails = StripeAPI.BillingDetails() + billingDetails.address = billingAddress + billingDetails.email = "email@email.com" + billingDetails.name = "Isaac Asimov" + billingDetails.phone = "555-555-5555" + + params.card = card + params.billingDetails = billingDetails + + StripeAPI.PaymentMethod.create(apiClient: apiClient, params: params) { result in + let paymentMethod = try! result.get() + XCTAssertEqual(paymentMethod.card?.last4, "4242") + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateCardPaymentMethodWithAdditionalAPIStuff() { + let expectation = self.expectation(description: "Created") + let apiClient = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + var params = StripeAPI.PaymentMethodParams(type: .card) + var card = StripeAPI.PaymentMethodParams.Card() + card.number = "4242424242424242" + card.expYear = 28 + card.expMonth = 12 + card.cvc = "100" + var billingAddress = StripeAPI.BillingDetails.Address() + billingAddress.city = "San Francisco" + billingAddress.country = "US" + billingAddress.line1 = "150 Townsend St" + billingAddress.line2 = "4th Floor" + billingAddress.postalCode = "94103" + billingAddress.state = "CA" + billingAddress.additionalParameters = ["invalid_thing": "yes"] + + var billingDetails = StripeAPI.BillingDetails() + billingDetails.address = billingAddress + billingDetails.email = "email@email.com" + billingDetails.name = "Isaac Asimov" + billingDetails.phone = "555-555-5555" + + params.card = card + params.billingDetails = billingDetails + + StripeAPI.PaymentMethod.create(apiClient: apiClient, params: params) { result in + do { + _ = try result.get() + } + catch { + let stripeError = error as? StripeError + if case .apiError(let apiError) = stripeError { + XCTAssertEqual(apiError.code, "parameter_unknown") + XCTAssertEqual(apiError.param, "billing_details[address][invalid_thing]") + expectation.fulfill() + } + } + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/StripeCore/StripeCore.xcodeproj/project.pbxproj b/StripeCore/StripeCore.xcodeproj/project.pbxproj index 61bb406be9e..e371d1472a9 100644 --- a/StripeCore/StripeCore.xcodeproj/project.pbxproj +++ b/StripeCore/StripeCore.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 310AF470271E12F6007339F4 /* Dictionary+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310AF46F271E12F6007339F4 /* Dictionary+Stripe.swift */; }; 31337A4926E04B6A005C7E02 /* URLSession+Retry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31337A4826E04B6A005C7E02 /* URLSession+Retry.swift */; }; 317C4FE0275FF44D003771D7 /* InstallMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317C4FDF275FF44D003771D7 /* InstallMethod.swift */; }; + 315BDBDF2788E2B4007BD11F /* STPDispatchFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315BDBDE2788E2B4007BD11F /* STPDispatchFunctions.swift */; }; 319E36582719EBF700460867 /* ServerErrorMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319E36572719EBF700460867 /* ServerErrorMapper.swift */; }; 31A5269226C46D9600F8AB59 /* STPAppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A5269126C46D9500F8AB59 /* STPAppInfo.swift */; }; 31A5269426C46E2C00F8AB59 /* StripeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A5269326C46E2C00F8AB59 /* StripeAPI.swift */; }; @@ -19,6 +20,8 @@ 31B49EA426E95DDA00A0464A /* STPURLCallbackHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B49EA326E95DDA00A0464A /* STPURLCallbackHandler.swift */; }; 31B49EA626E95E0600A0464A /* NSURLComponents+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B49EA526E95E0600A0464A /* NSURLComponents+Stripe.swift */; }; 31B4A0FF271F3FD200832DBD /* STPError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B49EA926E9821100A0464A /* STPError.swift */; }; + 31E6D962274444E300A89B6D /* STPNumericStringValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D961274444E300A89B6D /* STPNumericStringValidator.swift */; }; + 31E6D9642744451B00A89B6D /* NSCharacterSet+StripeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E6D9632744451B00A89B6D /* NSCharacterSet+StripeCore.swift */; }; 31FDFF3226C5E37100E4B743 /* NSError+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FDFF3126C5E37100E4B743 /* NSError+Stripe.swift */; }; 363B92662743211C00BA52EC /* Enums+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 363B92652743211C00BA52EC /* Enums+CustomStringConvertible.swift */; }; 6142353626E12272004E4B37 /* AnalyticLoggableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6142353526E12272004E4B37 /* AnalyticLoggableError.swift */; }; @@ -108,6 +111,7 @@ 310AF46F271E12F6007339F4 /* Dictionary+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+Stripe.swift"; sourceTree = ""; }; 31337A4826E04B6A005C7E02 /* URLSession+Retry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSession+Retry.swift"; sourceTree = ""; }; 317C4FDF275FF44D003771D7 /* InstallMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallMethod.swift; sourceTree = ""; }; + 315BDBDE2788E2B4007BD11F /* STPDispatchFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPDispatchFunctions.swift; sourceTree = ""; }; 319E36572719EBF700460867 /* ServerErrorMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerErrorMapper.swift; sourceTree = ""; }; 31A5268D26C46CFE00F8AB59 /* StripeCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeCodable.swift; sourceTree = ""; }; 31A5269126C46D9500F8AB59 /* STPAppInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPAppInfo.swift; sourceTree = ""; }; @@ -120,6 +124,8 @@ 31B49EA326E95DDA00A0464A /* STPURLCallbackHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPURLCallbackHandler.swift; sourceTree = ""; }; 31B49EA526E95E0600A0464A /* NSURLComponents+Stripe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSURLComponents+Stripe.swift"; sourceTree = ""; }; 31B49EA926E9821100A0464A /* STPError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPError.swift; sourceTree = ""; }; + 31E6D961274444E300A89B6D /* STPNumericStringValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPNumericStringValidator.swift; sourceTree = ""; }; + 31E6D9632744451B00A89B6D /* NSCharacterSet+StripeCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSCharacterSet+StripeCore.swift"; sourceTree = ""; }; 31FDFF3126C5E37100E4B743 /* NSError+Stripe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+Stripe.swift"; sourceTree = ""; }; 36064BA326D6E6F4002A8AAA /* ms-MY */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ms-MY"; path = "ms-MY.lproj/Localizable.strings"; sourceTree = ""; }; 36064BAB26D830B2002A8AAA /* bg-BG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "bg-BG"; path = "bg-BG.lproj/Localizable.strings"; sourceTree = ""; }; @@ -306,6 +312,7 @@ E614AC9D268BE14B00C59B3D /* NSMutableURLRequest+Stripe.swift */, 31B49EA526E95E0600A0464A /* NSURLComponents+Stripe.swift */, E6752D7726F413A00062B821 /* String+StripeCore.swift */, + 31E6D9632744451B00A89B6D /* NSCharacterSet+StripeCore.swift */, E666DF25273B9D0200638B60 /* UIImage+StripeCore.swift */, ); path = Categories; @@ -318,10 +325,12 @@ E6548EE72728D39500F399B2 /* Async.swift */, E66784B026980677005F7CC8 /* BundleLocatorProtocol.swift */, 319E36572719EBF700460867 /* ServerErrorMapper.swift */, + 315BDBDE2788E2B4007BD11F /* STPDispatchFunctions.swift */, E6539B3F269E4D9200E05D26 /* STPDeviceUtils.swift */, 31B49EA926E9821100A0464A /* STPError.swift */, 31A5269526C46EBD00F8AB59 /* STPURLCallbackHandler.swift */, E66784AF26980677005F7CC8 /* StripeCoreBundleLocator.swift */, + 31E6D961274444E300A89B6D /* STPNumericStringValidator.swift */, E614AC9F268BE14B00C59B3D /* URLEncoder.swift */, 31337A4826E04B6A005C7E02 /* URLSession+Retry.swift */, 31B49EA326E95DDA00A0464A /* STPURLCallbackHandler.swift */, @@ -400,13 +409,6 @@ path = Models; sourceTree = ""; }; - E66784AD26980411005F7CC8 /* Recovered References */ = { - isa = PBXGroup; - children = ( - ); - name = "Recovered References"; - sourceTree = ""; - }; E67A1E5F27404FB500977F63 /* Mock Files */ = { isa = PBXGroup; children = ( @@ -423,7 +425,6 @@ E69D640826855B260090B43D /* StripeCoreTests */, E6FB9BB9268EA95F000FDB4F /* StripeCoreTestUtils */, E69D63FC26855B250090B43D /* Products */, - E66784AD26980411005F7CC8 /* Recovered References */, E6233B32269E62330062005D /* Frameworks */, ); sourceTree = ""; @@ -756,6 +757,7 @@ buildActionMask = 2147483647; files = ( F338B9832748809200E9323D /* EmptyResponse.swift in Sources */, + 31E6D9642744451B00A89B6D /* NSCharacterSet+StripeCore.swift in Sources */, E6752D7826F413A00062B821 /* String+StripeCore.swift in Sources */, 319E36582719EBF700460867 /* ServerErrorMapper.swift in Sources */, E6598C8F269615E000278740 /* STPLocalizedString.swift in Sources */, @@ -785,6 +787,7 @@ 363B92662743211C00BA52EC /* Enums+CustomStringConvertible.swift in Sources */, E61ADACB270E6146004ED998 /* NSArray+Stripe.swift in Sources */, E65C44C0273B9DB500F753BC /* STPMultipartFormDataEncoder.swift in Sources */, + 315BDBDF2788E2B4007BD11F /* STPDispatchFunctions.swift in Sources */, 6142353626E12272004E4B37 /* AnalyticLoggableError.swift in Sources */, E620C243269F9B210054DD6D /* STPDeviceUtils.swift in Sources */, 31A5269C26C478B000F8AB59 /* StripeServiceError.swift in Sources */, @@ -796,6 +799,7 @@ E614ACA4268BE14B00C59B3D /* NSBundle+Stripe_AppName.swift in Sources */, 31FDFF3226C5E37100E4B743 /* NSError+Stripe.swift in Sources */, E614ACA2268BE14B00C59B3D /* Analytic.swift in Sources */, + 31E6D962274444E300A89B6D /* STPNumericStringValidator.swift in Sources */, 31B49E9E26E951EB00A0464A /* STPAPIClient.swift in Sources */, E614ACA1268BE14B00C59B3D /* STPAnalyticEvent.swift in Sources */, ); diff --git a/StripeCore/StripeCore/Source/API Bindings/STPAPIClient.swift b/StripeCore/StripeCore/Source/API Bindings/STPAPIClient.swift index f1343b100f0..f43f2f3be95 100644 --- a/StripeCore/StripeCore/Source/API Bindings/STPAPIClient.swift +++ b/StripeCore/StripeCore/Source/API Bindings/STPAPIClient.swift @@ -149,19 +149,6 @@ public class STPAPIClient { } #endif } - - @_spi(STP) public static var paymentUserAgent: String { - var paymentUserAgent = "stripe-ios/\(STPAPIClient.STPSDKVersion)" - let components = [paymentUserAgent] + STPAnalyticsClient.sharedClient.productUsage - paymentUserAgent = components.joined(separator: "; ") - return paymentUserAgent - } - - @_spi(STP) public class func paramsAddingPaymentUserAgent(_ params: [String: Any]) -> [String: Any] { - var newParams = params - newParams["payment_user_agent"] = Self.paymentUserAgent - return newParams - } class func stripeUserAgentDetails(with appInfo: STPAppInfo?) -> String { var details: [String: String] = [ diff --git a/StripeCore/StripeCore/Source/API Bindings/StripeCodable.swift b/StripeCore/StripeCore/Source/API Bindings/StripeCodable.swift index c9809d758b6..0949331ae00 100644 --- a/StripeCore/StripeCore/Source/API Bindings/StripeCodable.swift +++ b/StripeCore/StripeCore/Source/API Bindings/StripeCodable.swift @@ -54,8 +54,7 @@ public protocol StripeEnumCodable: Codable { extension StripeDecodable { /// A dictionary containing all response fields from the original JSON, /// including unknown fields. - // TODO: Make this `public internal(set)` when releasing StripeCodable API objects. - var allResponseFields: [String: Any] { + public internal(set) var allResponseFields: [String: Any] { get { self._allResponseFieldsStorage?.storage ?? [:] } @@ -78,8 +77,7 @@ extension StripeEncodable { /// // add card values /// cardParams.additionalParameters = ["test_field": "example_value"] /// PaymentsAPI.shared.createToken(withParameters: cardParams completion:...); - // TODO: Make this `public` when releasing StripeCodable API objects. - var additionalParameters: [String: Any] { + public var additionalParameters: [String: Any] { get { self._additionalParametersStorage?.storage ?? [:] } diff --git a/StripeCore/StripeCore/Source/API Bindings/StripeError.swift b/StripeCore/StripeCore/Source/API Bindings/StripeError.swift index bf6ee539f29..aab286a6048 100644 --- a/StripeCore/StripeCore/Source/API Bindings/StripeError.swift +++ b/StripeCore/StripeCore/Source/API Bindings/StripeError.swift @@ -8,10 +8,13 @@ import Foundation /// Error codes returned from STPAPIClient -enum StripeError: Error { +@_spi(STP) public enum StripeError: Error { /// The server returned an API error case apiError(StripeAPIError) + /// The request was invalid + case invalidRequest + /// Localized description of the error public var localizedDescription: String { return errorDescription ?? NSError.stp_unexpectedErrorMessage() @@ -21,31 +24,39 @@ enum StripeError: Error { // MARK: - LocalizedError extension StripeError: LocalizedError { - var errorDescription: String? { + @_spi(STP) public var errorDescription: String? { switch self { case .apiError(let apiError): return apiError.errorUserInfoString(key: NSLocalizedDescriptionKey) + case .invalidRequest: + return nil } } - var failureReason: String? { + @_spi(STP) public var failureReason: String? { switch self { case .apiError(let apiError): return apiError.errorUserInfoString(key: NSLocalizedFailureReasonErrorKey) + case .invalidRequest: + return nil } } - var recoverySuggestion: String? { + @_spi(STP) public var recoverySuggestion: String? { switch self { case .apiError(let apiError): return apiError.errorUserInfoString(key: NSLocalizedRecoverySuggestionErrorKey) + case .invalidRequest: + return nil } } - var helpAnchor: String? { + @_spi(STP) public var helpAnchor: String? { switch self { case .apiError(let apiError): return apiError.errorUserInfoString(key: NSHelpAnchorErrorKey) + case .invalidRequest: + return nil } } } diff --git a/StripeCore/StripeCore/Source/API Bindings/StripeServiceError.swift b/StripeCore/StripeCore/Source/API Bindings/StripeServiceError.swift index 8db8fad6071..6350aea8378 100644 --- a/StripeCore/StripeCore/Source/API Bindings/StripeServiceError.swift +++ b/StripeCore/StripeCore/Source/API Bindings/StripeServiceError.swift @@ -9,38 +9,38 @@ import Foundation /// An error returned from the Stripe API. /// https://stripe.com/docs/api/errors -struct StripeAPIError: StripeDecodable { +@_spi(STP) public struct StripeAPIError: StripeDecodable { /// The type of error returned. - var type: ErrorType + @_spi(STP) public var type: ErrorType /// For some errors that could be handled programmatically, a short string indicating the error code reported. /// https://stripe.com/docs/error-codes - var code: String? + @_spi(STP) public var code: String? /// A URL to more information about the error code reported. - var docUrl: URL? + @_spi(STP) public var docUrl: URL? /// A human-readable message providing more details about the error. For card errors, these messages can be shown to your users. - var message: String? + @_spi(STP) public var message: String? /// If the error is parameter-specific, the parameter related to the error. For example, you can use this to display a message near the correct form field. - var param: String? + @_spi(STP) public var param: String? // More information may be available in `allResponseFields`, including // the PaymentIntent or PaymentMethod. /// Types of errors presented by the API. - enum ErrorType: String, Decodable { + @_spi(STP) public enum ErrorType: String, Decodable { case apiError = "api_error" case cardError = "card_error" case idempotencyError = "idempotency_error" case invalidRequestError = "invalid_request_error" } - var _allResponseFieldsStorage: NonEncodableParameters? + public var _allResponseFieldsStorage: NonEncodableParameters? } -struct StripeAPIErrorResponse: StripeDecodable { +@_spi(STP) public struct StripeAPIErrorResponse: StripeDecodable { @IncludeUnknownFields - var error: StripeAPIError? + @_spi(STP) public var error: StripeAPIError? - var _allResponseFieldsStorage: NonEncodableParameters? + public var _allResponseFieldsStorage: NonEncodableParameters? } extension NSError { diff --git a/StripeUICore/StripeUICore/Source/Categories/NSCharacterSet+StripeUICore.swift b/StripeCore/StripeCore/Source/Categories/NSCharacterSet+StripeCore.swift similarity index 94% rename from StripeUICore/StripeUICore/Source/Categories/NSCharacterSet+StripeUICore.swift rename to StripeCore/StripeCore/Source/Categories/NSCharacterSet+StripeCore.swift index e99d8f34238..0cbc30367fd 100644 --- a/StripeUICore/StripeUICore/Source/Categories/NSCharacterSet+StripeUICore.swift +++ b/StripeCore/StripeCore/Source/Categories/NSCharacterSet+StripeCore.swift @@ -1,5 +1,5 @@ // -// NSCharacterSet+StripeUICore.swift +// NSCharacterSet+StripeCore.swift // StripeUICore // // Created by Brian Dorfman on 6/9/17. diff --git a/StripeCore/StripeCore/Source/Categories/String+StripeCore.swift b/StripeCore/StripeCore/Source/Categories/String+StripeCore.swift index be8f009fef9..cd9524638cd 100644 --- a/StripeCore/StripeCore/Source/Categories/String+StripeCore.swift +++ b/StripeCore/StripeCore/Source/Categories/String+StripeCore.swift @@ -16,3 +16,14 @@ import Foundation return self.hasPrefix("sk_") } } + +@_spi(STP) public func stringIfHasContentsElseNil(_ string: String?) -> // MARK: - + String? +{ + guard let string = string, + !string.isEmpty + else { + return nil + } + return string +} diff --git a/Stripe/STPDispatchFunctions.swift b/StripeCore/StripeCore/Source/Helpers/STPDispatchFunctions.swift similarity index 75% rename from Stripe/STPDispatchFunctions.swift rename to StripeCore/StripeCore/Source/Helpers/STPDispatchFunctions.swift index b9c3ed8c505..d1efe1a311e 100644 --- a/Stripe/STPDispatchFunctions.swift +++ b/StripeCore/StripeCore/Source/Helpers/STPDispatchFunctions.swift @@ -8,7 +8,7 @@ import Foundation -func stpDispatchToMainThreadIfNecessary(_ block: @escaping () -> Void) { +@_spi(STP) public func stpDispatchToMainThreadIfNecessary(_ block: @escaping () -> Void) { if Thread.isMainThread { block() } else { diff --git a/StripeCore/StripeCore/Source/Helpers/STPError.swift b/StripeCore/StripeCore/Source/Helpers/STPError.swift index 044da27af66..d43053e0907 100644 --- a/StripeCore/StripeCore/Source/Helpers/STPError.swift +++ b/StripeCore/StripeCore/Source/Helpers/STPError.swift @@ -37,6 +37,9 @@ public class STPError { /// All Stripe iOS errors will be under this domain. public static let stripeDomain = "com.stripe.lib" + /// The error domain for errors in `STPPaymentHandler`. + @objc public static let STPPaymentHandlerErrorDomain = "STPPaymentHandlerErrorDomain" + /// A human-readable message providing more details about the error. /// For card errors, these messages can be shown to your users. /// - seealso: https://stripe.com/docs/api/errors#errors-message @@ -63,6 +66,17 @@ public class STPError { /// NSError extensions for creating error objects from Stripe API responses. extension NSError { + @_spi(STP) public static func stp_error(from modernStripeError: StripeError) -> NSError? { + switch modernStripeError { + case .apiError(let stripeAPIError): + return stp_error(fromStripeResponse: ["error": stripeAPIError.allResponseFields]) + case .invalidRequest: + return NSError( + domain: STPError.stripeDomain, code: STPErrorCode.invalidRequestError.rawValue, + userInfo: nil) + } + } + @_spi(STP) public static func stp_error( errorType: String?, stripeErrorCode: String?, diff --git a/Stripe/STPNumericStringValidator.swift b/StripeCore/StripeCore/Source/Helpers/STPNumericStringValidator.swift similarity index 70% rename from Stripe/STPNumericStringValidator.swift rename to StripeCore/StripeCore/Source/Helpers/STPNumericStringValidator.swift index cb5d818856e..5a5024d1280 100644 --- a/Stripe/STPNumericStringValidator.swift +++ b/StripeCore/StripeCore/Source/Helpers/STPNumericStringValidator.swift @@ -7,19 +7,17 @@ // import Foundation -@_spi(STP) import StripeCore -@_spi(STP) import StripeUICore -enum STPTextValidationState: Int { +@_spi(STP) public enum STPTextValidationState: Int { case empty case incomplete case complete case invalid } -class STPNumericStringValidator: NSObject { +@_spi(STP) open class STPNumericStringValidator: NSObject { /// Whether or not the target string contains only numeric characters. - class func isStringNumeric(_ string: String) -> Bool { + @_spi(STP) public class func isStringNumeric(_ string: String) -> Bool { return (string as NSString).rangeOfCharacter(from: CharacterSet.stp_invertedAsciiDigit) .location @@ -27,7 +25,7 @@ class STPNumericStringValidator: NSObject { } /// Returns a copy of the passed string with all non-numeric characters removed. - class func sanitizedNumericString(for string: String) -> String { + @_spi(STP) public class func sanitizedNumericString(for string: String) -> String { return string.stp_stringByRemovingCharacters(from: CharacterSet.stp_invertedAsciiDigit) } } diff --git a/StripeUICore/StripeUICore.xcodeproj/project.pbxproj b/StripeUICore/StripeUICore.xcodeproj/project.pbxproj index e555163f72e..1bca4e62d66 100644 --- a/StripeUICore/StripeUICore.xcodeproj/project.pbxproj +++ b/StripeUICore/StripeUICore.xcodeproj/project.pbxproj @@ -30,7 +30,6 @@ E60564CC27056DD80023B0B6 /* Locale+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E60564CB27056DD80023B0B6 /* Locale+StripeUICore.swift */; }; E60693712703B0E500742859 /* DropdownFieldElement+AddressFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E60693702703B0E500742859 /* DropdownFieldElement+AddressFactory.swift */; }; E606937327041A4400742859 /* TextFieldFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E606937227041A4400742859 /* TextFieldFormatter.swift */; }; - E60693752704218600742859 /* NSCharacterSet+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E60693742704218600742859 /* NSCharacterSet+StripeUICore.swift */; }; E606937727042AA400742859 /* Locale+StripeUICoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E606937627042AA400742859 /* Locale+StripeUICoreTests.swift */; }; E606937C27043BD900742859 /* IDNumberTextFieldConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E606937B27043BD900742859 /* IDNumberTextFieldConfigurationTest.swift */; }; E606937E27043C2700742859 /* TextFieldFormatterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E606937D27043C2700742859 /* TextFieldFormatterTest.swift */; }; @@ -137,7 +136,6 @@ E60564CB27056DD80023B0B6 /* Locale+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+StripeUICore.swift"; sourceTree = ""; }; E60693702703B0E500742859 /* DropdownFieldElement+AddressFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DropdownFieldElement+AddressFactory.swift"; sourceTree = ""; }; E606937227041A4400742859 /* TextFieldFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldFormatter.swift; sourceTree = ""; }; - E60693742704218600742859 /* NSCharacterSet+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSCharacterSet+StripeUICore.swift"; sourceTree = ""; }; E606937627042AA400742859 /* Locale+StripeUICoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+StripeUICoreTests.swift"; sourceTree = ""; }; E606937B27043BD900742859 /* IDNumberTextFieldConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IDNumberTextFieldConfigurationTest.swift; sourceTree = ""; }; E606937D27043C2700742859 /* TextFieldFormatterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldFormatterTest.swift; sourceTree = ""; }; @@ -791,7 +789,6 @@ E6B0DDD22714C49400CE86DC /* UIKeyboardType+StripeUICore.swift in Sources */, E6752DA926F420F70062B821 /* TextFieldElement+Validation.swift in Sources */, E61ADA8C2707DBBD004ED998 /* DateFieldElement.swift in Sources */, - E60693752704218600742859 /* NSCharacterSet+StripeUICore.swift in Sources */, E6752D9E26F420F70062B821 /* FormElement.swift in Sources */, E6752D7526F412FF0062B821 /* ElementsUI.swift in Sources */, E6752DA526F420F70062B821 /* StaticElement.swift in Sources */, diff --git a/Tests/Tests/FraudDetectionDataTest.swift b/Tests/Tests/FraudDetectionDataTest.swift index 87a6692d384..367d596e01d 100644 --- a/Tests/Tests/FraudDetectionDataTest.swift +++ b/Tests/Tests/FraudDetectionDataTest.swift @@ -8,6 +8,7 @@ import XCTest @testable import Stripe +@testable @_spi(STP) import StripeApplePay class FraudDetectionDataTest: XCTestCase { func testResetsSIDIfExpired() { diff --git a/Tests/Tests/PKPayment+StripeTest.swift b/Tests/Tests/PKPayment+StripeTest.swift index 54d32c5f4da..3757878ccca 100644 --- a/Tests/Tests/PKPayment+StripeTest.swift +++ b/Tests/Tests/PKPayment+StripeTest.swift @@ -10,6 +10,7 @@ import PassKit import XCTest @testable import Stripe +@_spi(STP) import StripeApplePay class PKPayment_StripeTest: XCTestCase { func testIsSimulated() { diff --git a/Tests/Tests/STPAPIClientTest.swift b/Tests/Tests/STPAPIClientTest.swift index 42f69c8aeb8..ce3158063dd 100644 --- a/Tests/Tests/STPAPIClientTest.swift +++ b/Tests/Tests/STPAPIClientTest.swift @@ -10,6 +10,7 @@ import XCTest @testable @_spi(STP) import Stripe @testable @_spi(STP) import StripeCore +@testable @_spi(STP) import StripeApplePay class STPAPIClientTest: XCTestCase { func testSharedClient() { diff --git a/Tests/Tests/STPAnalyticsClientPaymentSheetTest.swift b/Tests/Tests/STPAnalyticsClientPaymentSheetTest.swift index 32e556dce15..9df85685fbe 100644 --- a/Tests/Tests/STPAnalyticsClientPaymentSheetTest.swift +++ b/Tests/Tests/STPAnalyticsClientPaymentSheetTest.swift @@ -120,6 +120,7 @@ class STPAnalyticsClientPaymentSheetTest: XCTestCase { // verify XCTAssertEqual(15, payload.count) XCTAssertNotNil(payload["device_type"] as? String) + // In xctest, this is the version of Xcode XCTAssertNotNil(payload["app_version"] as? String) XCTAssertEqual("none", payload["ocr_type"] as? String) XCTAssertEqual(STPAnalyticEvent.mcInitCompleteApplePay.rawValue, payload["event"] as? String) @@ -127,8 +128,9 @@ class STPAnalyticsClientPaymentSheetTest: XCTestCase { XCTAssertEqual("analytics.stripeios-1.0", payload["analytics_ua"] as? String) XCTAssertEqual("xctest", payload["app_name"] as? String) XCTAssertNotNil(payload["os_version"] as? String) - XCTAssertEqual("full", payload["ui_usage_level"] as? String) + XCTAssertNil(payload["ui_usage_level"]) XCTAssertTrue(payload["apple_pay_enabled"] as? Bool ?? false) + XCTAssertEqual("legacy", payload["pay_var"] as? String) XCTAssertEqual(STPAPIClient.STPSDKVersion, payload["bindings_version"] as? String) XCTAssertEqual("testVal", payload["testKey"] as? String) XCTAssertEqual("X", payload["install"] as? String) diff --git a/Tests/Tests/STPAnalyticsClientPaymentsTest.swift b/Tests/Tests/STPAnalyticsClientPaymentsTest.swift index cdb860bc0c5..ad3fd4f63b1 100644 --- a/Tests/Tests/STPAnalyticsClientPaymentsTest.swift +++ b/Tests/Tests/STPAnalyticsClientPaymentsTest.swift @@ -10,6 +10,7 @@ import XCTest @testable @_spi(STP) import StripeCore @testable @_spi(STP) import Stripe +@_spi(STP) import StripeApplePay class STPAnalyticsClientPaymentsTest: XCTestCase { private var client: STPAnalyticsClient! @@ -143,7 +144,7 @@ class STPAnalyticsClientPaymentsTest: XCTestCase { XCTAssertEqual(STPAnalyticsClient.tokenType(fromParameters: bankDict), "bank_account"); let applePay = STPFixtures.applePayPayment() - let applePayDict = addTelemetry(STPAPIClient.shared.parameters(for: applePay)) + let applePayDict = addTelemetry(applePay.stp_tokenParameters(apiClient: .shared)) XCTAssertEqual(STPAnalyticsClient.tokenType(fromParameters: applePayDict), "apple_pay") } diff --git a/Tests/Tests/STPApplePayContextFunctionalTest.m b/Tests/Tests/STPApplePayContextFunctionalTest.m index cc7b3e1bff4..5c05a1b28c8 100644 --- a/Tests/Tests/STPApplePayContextFunctionalTest.m +++ b/Tests/Tests/STPApplePayContextFunctionalTest.m @@ -74,7 +74,7 @@ - (void)setUp { self.context = [[STPApplePayContext alloc] initWithPaymentRequest:[STPFixtures applePayRequest] delegate:self.delegate]; self.apiClient.applePayContext = self.context; self.context.apiClient = self.apiClient; - self.context.authorizationController = [[STPTestPKPaymentAuthorizationController alloc] init]; + self.context._applePayContext.authorizationController = [[STPTestPKPaymentAuthorizationController alloc] init]; } - (void)tearDown { @@ -274,12 +274,12 @@ - (void)testCancelBeforeIntentConfirmsCancels { // Cancelling Apple Pay *before* the context attempts to confirms the PI/SI... STPTestApplePayContextDelegate *delegate = self.delegate; delegate.didCreatePaymentMethodDelegateMethod = ^(__unused STPPaymentMethod *paymentMethod, __unused PKPayment *paymentInformation, STPIntentClientSecretCompletionBlock completion) { - [self.context paymentAuthorizationControllerDidFinish:self.context.authorizationController]; // Simulate cancel before passing PI to the context + [self.context._applePayContext paymentAuthorizationControllerDidFinish:self.context._applePayContext.authorizationController]; // Simulate cancel before passing PI to the context // ...should never retrieve the PI (b/c it is cancelled before) completion(@"A 'client secret' that triggers an exception if fetched", nil); }; - [self.context paymentAuthorizationController:self.context.authorizationController + [self.context._applePayContext paymentAuthorizationController:self.context._applePayContext.authorizationController didAuthorizePayment:[STPFixtures simulatorApplePayPayment] handler:^(PKPaymentAuthorizationResult * __unused _Nonnull result) {}]; // Simulate user tapping 'Pay' button in Apple Pay @@ -309,7 +309,7 @@ - (void)testCancelAfterPaymentIntentConfirmsStillSucceeds { }]; }; - [self.context paymentAuthorizationController:self.context.authorizationController + [self.context._applePayContext paymentAuthorizationController:self.context._applePayContext.authorizationController didAuthorizePayment:[STPFixtures simulatorApplePayPayment] handler:^(PKPaymentAuthorizationResult * __unused _Nonnull result) {}]; // Simulate user tapping 'Pay' button in Apple Pay @@ -344,7 +344,7 @@ - (void)testCancelAfterSetupIntentConfirmsStillSucceeds { }]; }; - [self.context paymentAuthorizationController:self.context.authorizationController + [self.context._applePayContext paymentAuthorizationController:self.context._applePayContext.authorizationController didAuthorizePayment:[STPFixtures simulatorApplePayPayment] handler:^(PKPaymentAuthorizationResult * __unused _Nonnull result) {}]; // Simulate user tapping 'Pay' button in Apple Pay @@ -373,10 +373,10 @@ - (void)_startApplePayForContextWithExpectedStatus:(PKPaymentAuthorizationStatus // When the user taps 'Pay', PKPaymentAuthorizationController calls `didAuthorizePayment:completion:` // After you call its completion block, it calls `paymentAuthorizationControllerDidFinish:` XCTestExpectation *didCallAuthorizePaymentCompletion = [self expectationWithDescription:@"ApplePayContext called completion block of paymentAuthorizationController:didAuthorizePayment:completion:"]; - [self.context paymentAuthorizationController:self.context.authorizationController didAuthorizePayment:[STPFixtures simulatorApplePayPayment] handler:^(PKPaymentAuthorizationResult * _Nonnull result) { + [self.context._applePayContext paymentAuthorizationController:self.context._applePayContext.authorizationController didAuthorizePayment:[STPFixtures simulatorApplePayPayment] handler:^(PKPaymentAuthorizationResult * _Nonnull result) { XCTAssertEqual(expectedStatus, result.status); dispatch_async(dispatch_get_main_queue(), ^{ - [self.context paymentAuthorizationControllerDidFinish:self.context.authorizationController]; + [self.context._applePayContext paymentAuthorizationControllerDidFinish:self.context._applePayContext.authorizationController]; [didCallAuthorizePaymentCompletion fulfill]; }); }]; diff --git a/Tests/Tests/STPApplePayContextFunctionalTestExtras.swift b/Tests/Tests/STPApplePayContextFunctionalTestExtras.swift index 4c76528c3e5..f0de3e70f59 100644 --- a/Tests/Tests/STPApplePayContextFunctionalTestExtras.swift +++ b/Tests/Tests/STPApplePayContextFunctionalTestExtras.swift @@ -10,11 +10,12 @@ import Foundation @testable import Stripe @testable import StripeCore +@testable import StripeApplePay import OHHTTPStubs @available(iOS 13.0, *) class STPApplePayContextFunctionalTestAPIClient: _stpobjc_STPAPIClient { - @objc var applePayContext: STPApplePayContext + @objc var applePayContext: _stpobjc_APContext @objc var shouldSimulateCancelAfterConfirmBegins: Bool = false @objc func setupStubs() { @@ -24,7 +25,7 @@ class STPApplePayContextFunctionalTestAPIClient: _stpobjc_STPAPIClient { urlString.contains("_intents/"), urlString.hasSuffix("/confirm") { if self.shouldSimulateCancelAfterConfirmBegins { - self.applePayContext.paymentAuthorizationControllerDidFinish(self.applePayContext.authorizationController!) + self.applePayContext._applePayContext.paymentAuthorizationControllerDidFinish(self.applePayContext._applePayContext.authorizationController!) } } // Let everything pass through to the underlying API diff --git a/Tests/Tests/STPApplePayContextTest.swift b/Tests/Tests/STPApplePayContextTest.swift index 7a0343c6abe..0ee408bb33c 100644 --- a/Tests/Tests/STPApplePayContextTest.swift +++ b/Tests/Tests/STPApplePayContextTest.swift @@ -7,6 +7,7 @@ // @testable import Stripe +@_spi(STP) @testable import StripeApplePay class STPApplePayTestDelegateiOS11: NSObject, STPApplePayContextDelegate { func applePayContext( diff --git a/Tests/Tests/STPApplePayFunctionalTest.swift b/Tests/Tests/STPApplePayFunctionalTest.swift index 65f8eeb768e..0c4c19753f1 100644 --- a/Tests/Tests/STPApplePayFunctionalTest.swift +++ b/Tests/Tests/STPApplePayFunctionalTest.swift @@ -10,6 +10,7 @@ import PassKit import XCTest @testable import Stripe +@testable import StripeApplePay class STPApplePayFunctionalTest: STPNetworkStubbingTestCase { override func setUp() { @@ -18,7 +19,7 @@ class STPApplePayFunctionalTest: STPNetworkStubbingTestCase { } // TODO: regenerate these fixtures with a fresh/real PKPayment - func testCreateTokenWithPayment() { + func testCreateTokenWithPaymentClassic() { let payment = STPFixtures.applePayPayment() let client = STPAPIClient(publishableKey: "pk_test_vOo1umqsYxSrP5UXfOeL3ecm") @@ -45,6 +46,34 @@ class STPApplePayFunctionalTest: STPNetworkStubbingTestCase { waitForExpectations(timeout: 5.0, handler: nil) } + func testCreateTokenWithPayment() { + let payment = STPFixtures.applePayPayment() + let client = STPAPIClient(publishableKey: "pk_test_vOo1umqsYxSrP5UXfOeL3ecm") + + let expectation = self.expectation(description: "Apple pay token creation") + StripeAPI.Token.create( + apiClient: client, + payment: payment + ) { result in + expectation.fulfill() + XCTAssertNil(try? result.get(), "token should be nil") + guard case .failure(let error) = result else { + XCTFail("error should not be nil") + return + } + + // Since we can't actually generate a new cryptogram in a CI environment, we should just post a blob of expired token data and + // make sure we get the "too long since tokenization" error. This at least asserts that our blob has been correctly formatted and + // can be decrypted by the backend. + XCTAssert( + ((error as NSError).userInfo[STPError.errorMessageKey] as? NSString)?.range( + of: "too long" + ).location != NSNotFound, + "Error is unrelated to 24-hour expiry: \(error)") + } + waitForExpectations(timeout: 5.0, handler: nil) + } + func testCreateSourceWithPayment() { let payment = STPFixtures.applePayPayment() let client = STPAPIClient(publishableKey: "pk_test_vOo1umqsYxSrP5UXfOeL3ecm") diff --git a/Tests/Tests/STPBECSDebitAccountNumberValidatorTests.swift b/Tests/Tests/STPBECSDebitAccountNumberValidatorTests.swift index c73beaf67da..dc425bd0d2d 100644 --- a/Tests/Tests/STPBECSDebitAccountNumberValidatorTests.swift +++ b/Tests/Tests/STPBECSDebitAccountNumberValidatorTests.swift @@ -7,6 +7,7 @@ // @testable import Stripe +@_spi(STP) import StripeCore class STPBECSDebitAccountNumberValidatorTests: XCTestCase { func testValidationStateForText() { diff --git a/Tests/Tests/STPBSBNumberValidatorTests.swift b/Tests/Tests/STPBSBNumberValidatorTests.swift index 3c965e6184e..92c1a02c42b 100644 --- a/Tests/Tests/STPBSBNumberValidatorTests.swift +++ b/Tests/Tests/STPBSBNumberValidatorTests.swift @@ -7,6 +7,7 @@ // @testable import Stripe +@_spi(STP) import StripeCore class STPBSBNumberValidatorTests: XCTestCase { func testValidationStateForText() { diff --git a/Tests/Tests/STPNumericStringValidatorTests.swift b/Tests/Tests/STPNumericStringValidatorTests.swift index f6de7e81324..b29739d379d 100644 --- a/Tests/Tests/STPNumericStringValidatorTests.swift +++ b/Tests/Tests/STPNumericStringValidatorTests.swift @@ -6,6 +6,7 @@ // Copyright © 2020 Stripe, Inc. All rights reserved. // @testable import Stripe +@_spi(STP) import StripeCore class STPNumericStringValidatorTests: XCTestCase { func testNumberSanitization() { diff --git a/Tests/Tests/STPRadarSessionFunctionalTest.swift b/Tests/Tests/STPRadarSessionFunctionalTest.swift index b4f3f82cfa4..a220bc75692 100644 --- a/Tests/Tests/STPRadarSessionFunctionalTest.swift +++ b/Tests/Tests/STPRadarSessionFunctionalTest.swift @@ -8,6 +8,7 @@ import XCTest @testable import Stripe +@testable @_spi(STP) import StripeApplePay class STPRadarSessionFunctionalTest: XCTestCase { func testCreateWithoutInitialFraudDetection() { diff --git a/Tests/Tests/UserDefaults+StripeTest.swift b/Tests/Tests/UserDefaults+StripeTest.swift index 11f406d7d72..ad0345adb3e 100644 --- a/Tests/Tests/UserDefaults+StripeTest.swift +++ b/Tests/Tests/UserDefaults+StripeTest.swift @@ -8,6 +8,7 @@ import XCTest @testable import Stripe +@testable @_spi(STP) import StripeApplePay class UserDefaults_StripeTest: XCTestCase { func testFraudDetectionData() throws { diff --git a/Tests/installation_tests/carthage/CarthageTest.xcodeproj/project.pbxproj b/Tests/installation_tests/carthage/CarthageTest.xcodeproj/project.pbxproj index 4712e5e1aa9..3c1f6c2478f 100644 --- a/Tests/installation_tests/carthage/CarthageTest.xcodeproj/project.pbxproj +++ b/Tests/installation_tests/carthage/CarthageTest.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 04E6FCEC1B714AC2000C8759 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 04E6FCEA1B714AC2000C8759 /* Main.storyboard */; }; 04E6FCEE1B714AC2000C8759 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 04E6FCED1B714AC2000C8759 /* Images.xcassets */; }; 04E6FCF11B714AC2000C8759 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 04E6FCEF1B714AC2000C8759 /* LaunchScreen.xib */; }; + 3137B69427437E3500CE7F5C /* StripeApplePay.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3137B69327437E3500CE7F5C /* StripeApplePay.xcframework */; }; 31CA744B25CCC669007FE8BF /* Stripe3DS2.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31CA744A25CCC669007FE8BF /* Stripe3DS2.xcframework */; }; 31FF1DB1255FA383000EF4B0 /* Stripe.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04E6FD061B714DDF000C8759 /* Stripe.xcframework */; }; 31FF1DB2255FA383000EF4B0 /* Stripe.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 04E6FD061B714DDF000C8759 /* Stripe.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -80,6 +81,7 @@ 31FF1DB1255FA383000EF4B0 /* Stripe.xcframework in Frameworks */, 3BD9808F27431DFE00B09BCD /* StripeCardScan.xcframework in Frameworks */, E6F05EAA2687C76F00614D61 /* StripeCore.xcframework in Frameworks */, + 3137B69427437E3500CE7F5C /* StripeApplePay.xcframework in Frameworks */, E6AA24ED2744ABE000FD205E /* StripeCameraCore.xcframework in Frameworks */, E6A36A5C26BA4E0E002A4427 /* StripeIdentity.xcframework in Frameworks */, 31CA744B25CCC669007FE8BF /* Stripe3DS2.xcframework in Frameworks */, diff --git a/Tests/installation_tests/carthage/CarthageTest/ViewController.swift b/Tests/installation_tests/carthage/CarthageTest/ViewController.swift index bd194bc4ecf..00fce887efa 100644 --- a/Tests/installation_tests/carthage/CarthageTest/ViewController.swift +++ b/Tests/installation_tests/carthage/CarthageTest/ViewController.swift @@ -9,6 +9,7 @@ import Stripe import StripeIdentity import StripeCardScan +import StripeApplePay import UIKit class ViewController: UIViewController { diff --git a/Tests/installation_tests/cocoapods/with_frameworks_objc/Podfile b/Tests/installation_tests/cocoapods/with_frameworks_objc/Podfile index 8c0780e8ddf..296ff25e6a0 100644 --- a/Tests/installation_tests/cocoapods/with_frameworks_objc/Podfile +++ b/Tests/installation_tests/cocoapods/with_frameworks_objc/Podfile @@ -5,6 +5,7 @@ target 'CocoapodsTest' do use_frameworks! pod 'Stripe', path: '../../../..' pod 'StripeCore', path: '../../../..' + pod 'StripeApplePay', path: '../../../..' pod 'StripeUICore', path: '../../../..' pod 'StripeCardScan', path: '../../../..' diff --git a/Tests/installation_tests/cocoapods/with_frameworks_swift/CocoapodsTest/ViewController.swift b/Tests/installation_tests/cocoapods/with_frameworks_swift/CocoapodsTest/ViewController.swift index 15b379157e1..cc206e54d49 100644 --- a/Tests/installation_tests/cocoapods/with_frameworks_swift/CocoapodsTest/ViewController.swift +++ b/Tests/installation_tests/cocoapods/with_frameworks_swift/CocoapodsTest/ViewController.swift @@ -9,6 +9,7 @@ import Stripe import StripeIdentity import StripeCardScan +import StripeApplePay import UIKit class ViewController: UIViewController { diff --git a/Tests/installation_tests/cocoapods/with_frameworks_swift/Podfile b/Tests/installation_tests/cocoapods/with_frameworks_swift/Podfile index 91559159d5e..4195f48b4eb 100644 --- a/Tests/installation_tests/cocoapods/with_frameworks_swift/Podfile +++ b/Tests/installation_tests/cocoapods/with_frameworks_swift/Podfile @@ -6,6 +6,7 @@ target 'CocoapodsTest' do pod 'Stripe', path: '../../../..' pod 'StripeIdentity', path: '../../../..' pod 'StripeCardScan', path: '../../../..' + pod 'StripeApplePay', path: '../../../..' pod 'StripeCameraCore', path: '../../../..' pod 'StripeCore', path: '../../../..' pod 'StripeUICore', path: '../../../..' diff --git a/Tests/installation_tests/cocoapods/without_frameworks_objc/Podfile b/Tests/installation_tests/cocoapods/without_frameworks_objc/Podfile index 84b90e5aa27..66b14fd9292 100644 --- a/Tests/installation_tests/cocoapods/without_frameworks_objc/Podfile +++ b/Tests/installation_tests/cocoapods/without_frameworks_objc/Podfile @@ -2,6 +2,7 @@ target 'CocoapodsTest' do platform :ios, '11.0' pod 'Stripe', path: '../../../..' pod 'StripeCore', path: '../../../..' + pod 'StripeApplePay', path: '../../../..' pod 'StripeUICore', path: '../../../..' pod 'StripeCardScan', path: '../../../..' diff --git a/Tests/installation_tests/swift_package_manager/with_swift/SPMTest.xcodeproj/project.pbxproj b/Tests/installation_tests/swift_package_manager/with_swift/SPMTest.xcodeproj/project.pbxproj index 100845d2d3a..23befcac5e5 100644 --- a/Tests/installation_tests/swift_package_manager/with_swift/SPMTest.xcodeproj/project.pbxproj +++ b/Tests/installation_tests/swift_package_manager/with_swift/SPMTest.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 3137B6922743799C00CE7F5C /* StripeApplePay in Frameworks */ = {isa = PBXBuildFile; productRef = 3137B6912743799C00CE7F5C /* StripeApplePay */; }; 317C4FFD27604B54003771D7 /* SPMAnalyticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317C4FFC27604B54003771D7 /* SPMAnalyticsTests.swift */; }; 31DC3DB12537D02000623028 /* Stripe in Frameworks */ = {isa = PBXBuildFile; productRef = 31DC3DB02537D02000623028 /* Stripe */; }; 3B3F01B927444C5800C53D34 /* StripeUICoreAssetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3F01B827444C5800C53D34 /* StripeUICoreAssetTests.swift */; }; @@ -71,6 +72,7 @@ buildActionMask = 2147483647; files = ( 3BD980892743194F00B09BCD /* StripeCardScan in Frameworks */, + 3137B6922743799C00CE7F5C /* StripeApplePay in Frameworks */, 31DC3DB12537D02000623028 /* Stripe in Frameworks */, E6A36A5A26BA4C34002A4427 /* StripeIdentity in Frameworks */, ); @@ -168,6 +170,7 @@ 31DC3DB02537D02000623028 /* Stripe */, E6A36A5926BA4C34002A4427 /* StripeIdentity */, 3BD980882743194F00B09BCD /* StripeCardScan */, + 3137B6912743799C00CE7F5C /* StripeApplePay */, ); productName = CocoapodsTest; productReference = C176898320616C7E003DE895 /* SPMTest.app */; @@ -522,6 +525,10 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ + 3137B6912743799C00CE7F5C /* StripeApplePay */ = { + isa = XCSwiftPackageProductDependency; + productName = StripeApplePay; + }; 31DC3DB02537D02000623028 /* Stripe */ = { isa = XCSwiftPackageProductDependency; productName = Stripe; diff --git a/Tests/installation_tests/swift_package_manager/with_swift/SPMTest/ViewController.swift b/Tests/installation_tests/swift_package_manager/with_swift/SPMTest/ViewController.swift index 7f66e96c0d1..911ca849461 100644 --- a/Tests/installation_tests/swift_package_manager/with_swift/SPMTest/ViewController.swift +++ b/Tests/installation_tests/swift_package_manager/with_swift/SPMTest/ViewController.swift @@ -10,6 +10,7 @@ import UIKit import Stripe import StripeIdentity import StripeCardScan +import StripeApplePay class ViewController: UIViewController { diff --git a/Tests/recorded_network_traffic/STPApplePayFunctionalTest/testCreateTokenWithPaymentClassic/post_v1_tokens_0.tail b/Tests/recorded_network_traffic/STPApplePayFunctionalTest/testCreateTokenWithPaymentClassic/post_v1_tokens_0.tail new file mode 100644 index 00000000000..6a0490415dc --- /dev/null +++ b/Tests/recorded_network_traffic/STPApplePayFunctionalTest/testCreateTokenWithPaymentClassic/post_v1_tokens_0.tail @@ -0,0 +1,25 @@ +POST +/v1/tokens$ +400 +application/json +Content-Type: application/json +Access-Control-Allow-Origin: * +access-control-allow-methods: GET, POST, HEAD, OPTIONS, DELETE +Server: nginx +access-control-expose-headers: Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +Cache-Control: no-cache, no-store +Date: Wed, 24 Jul 2019 23:15:48 GMT +stripe-version: 2019-05-16 +access-control-allow-credentials: true +Content-Length: 213 +Strict-Transport-Security: max-age=31556926; includeSubDomains; preload +Connection: keep-alive +request-id: req_zWJiyZuMbx4GGM + +{ + "error" : { + "message" : "It's been too long since the user approved this payment in your app. You have to create a token within 24 hours of their in-app approval.", + "type" : "invalid_request_error" + } +} \ No newline at end of file diff --git a/ci_scripts/objc_bridging_checker.rb b/ci_scripts/objc_bridging_checker.rb index e5a46b76d01..1d94c5205e2 100755 --- a/ci_scripts/objc_bridging_checker.rb +++ b/ci_scripts/objc_bridging_checker.rb @@ -19,7 +19,7 @@ MISSING_PREFIX_REGEX = %r{ @(interface|protocol)\s+ # Find @interface or @protocol - \b(?!STP|Stripe|UI|PK|NS)(\w+)\b # Followed by a word that does not begin with STP, Stripe, UI, PK, or NS + \b(?!STP|Stripe|_stpinternal|UI|PK|NS)(\w+)\b # Followed by a word that does not begin with STP, Stripe, _stpinternal, UI, PK, or NS (?!.*\(SWIFT_EXTENSION) # And that isn't an extension }x diff --git a/fastlane/Fastfile b/fastlane/Fastfile index e5962902e1d..1c2162ac3db 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -50,6 +50,7 @@ platform :ios do sh("./ci_scripts/test.rb --build-only --scheme StripeCore") sh("./ci_scripts/test.rb --build-only --scheme StripeUICore") sh("./ci_scripts/test.rb --build-only --scheme StripeCameraCore") + sh("./ci_scripts/test.rb --build-only --scheme StripeApplePay") sh("./ci_scripts/test.rb --build-only --scheme StripeiOS") sh("./ci_scripts/test.rb --build-only --scheme StripeIdentity") sh("./ci_scripts/test.rb --build-only --scheme StripeCardScan") @@ -145,6 +146,7 @@ platform :ios do sh("./ci_scripts/test.rb --scheme 'StripeCameraCore' --device 'iPhone 6' --version 11.4 --skip-snapshot-tests") sh("./ci_scripts/test.rb --scheme 'StripeCore' --device 'iPhone 6' --version 11.4 --skip-snapshot-tests") sh("./ci_scripts/test.rb --scheme 'StripeUICore' --device 'iPhone 6' --version 11.4 --skip-snapshot-tests") + sh("./ci_scripts/test.rb --scheme 'StripeApplePay' --device 'iPhone 6' --version 11.4 --skip-snapshot-tests") sh("./ci_scripts/test.rb --scheme 'StripeCardScan' --device 'iPhone 6' --version 11.4 --skip-snapshot-tests") end end @@ -155,6 +157,7 @@ platform :ios do sh("./ci_scripts/test.rb --scheme 'StripeCameraCore' --device 'iPhone 6' --version 12.4 --skip-snapshot-tests") sh("./ci_scripts/test.rb --scheme 'StripeCore' --device 'iPhone 6' --version 12.4 --skip-snapshot-tests") sh("./ci_scripts/test.rb --scheme 'StripeUICore' --device 'iPhone 6' --version 12.4 --skip-snapshot-tests") + sh("./ci_scripts/test.rb --scheme 'StripeApplePay' --device 'iPhone 6' --version 12.4 --skip-snapshot-tests") sh("./ci_scripts/test.rb --scheme 'StripeCardScan' --device 'iPhone 6' --version 12.4 --skip-snapshot-tests") end end diff --git a/modules.yaml b/modules.yaml index a0028181374..69cb69974f2 100644 --- a/modules.yaml +++ b/modules.yaml @@ -86,6 +86,11 @@ modules: output: docs/stripe-identity readme: StripeIdentity/README.md +- podspec: StripeApplePay.podspec + scheme: StripeApplePay + framework_name: StripeApplePay + supports_catalyst: true + - podspec: StripeCardScan.podspec scheme: StripeCardScan framework_name: StripeCardScan @@ -106,6 +111,7 @@ pod_push_order: - StripeCore.podspec - StripeUICore.podspec - StripeCameraCore.podspec +- StripeApplePay.podspec - Stripe.podspec - StripeIdentity.podspec - StripeCardScan.podspec