├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── pull_request_template.md └── workflows │ ├── build.yml │ ├── release.yml │ ├── swiftlint.yml │ └── tests.yml ├── .gitignore ├── .swiftlint.yml ├── CHANGELOG.md ├── DEVELOPMENT.md ├── Demo ├── Demo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Demo.xcscheme ├── Demo │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ └── payments-sdk-app-icon.png │ │ └── Contents.json │ ├── Card │ │ ├── CardFormatter.swift │ │ └── CardType.swift │ ├── CardPayments │ │ ├── CardPaymentViewModel │ │ │ ├── CardPaymentState.swift │ │ │ └── CardPaymentViewModel.swift │ │ └── CardViewComponents │ │ │ ├── CardOrderActionButton.swift │ │ │ ├── CardOrderApproveView.swift │ │ │ ├── CardPaymentOrderCompletionView.swift │ │ │ ├── CardPaymentView.swift │ │ │ ├── CardPaymentsResultViews │ │ │ ├── CardApprovalResultView.swift │ │ │ ├── CardOrderCompletionResultView.swift │ │ │ └── OrderCreateCardResultView.swift │ │ │ └── CreateOrderCardPaymentView.swift │ ├── CardVault │ │ ├── CardVaultViewModel │ │ │ └── CardVaultViewModel.swift │ │ └── CardVaultViews │ │ │ ├── CardVaultView.swift │ │ │ ├── UpdateSetupTokenResultView.swift │ │ │ └── UpdateSetupTokenView.swift │ ├── Demo.entitlements │ ├── DemoApp.swift │ ├── DemoSettings │ │ ├── DemoSettings.swift │ │ ├── Environment.swift │ │ └── Intent.swift │ ├── Extensions │ │ ├── CardExtensions.swift │ │ └── PaymentButtonEnums+Extension.swift │ ├── Info.plist │ ├── LaunchScreen.storyboard │ ├── Models │ │ ├── ApprovalResult.swift │ │ ├── ClientIDRequest.swift │ │ ├── ClientIDResponse.swift │ │ ├── CreateOrderParams.swift │ │ ├── CreateSetupTokenParam.swift │ │ ├── CreateSetupTokenResponse.swift │ │ ├── EmptyBodyParams.swift │ │ ├── Order.swift │ │ ├── PaymentTokenParam.swift │ │ └── PaymentTokenResponse.swift │ ├── Networking │ │ ├── DemoMerchantAPI.swift │ │ ├── MerchantIntegration.swift │ │ └── URLResponseError.swift │ ├── PayPalVault │ │ ├── PayPalVaultViewModel │ │ │ └── PayPalVaultViewModel.swift │ │ └── PayPalVaultViews │ │ │ ├── PayPalVaultResultView.swift │ │ │ └── PayPalVaultView.swift │ ├── PayPalWebPayments │ │ ├── PayPalWebPaymentsView │ │ │ ├── PayPalWebButtonsView.swift │ │ │ ├── PayPalWebCreateOrderView.swift │ │ │ ├── PayPalWebPaymentsView.swift │ │ │ └── PayPalWebResultViews │ │ │ │ ├── PayPalApprovalResultView.swift │ │ │ │ ├── PayPalOrderCompletionResultView.swift │ │ │ │ ├── PayPalOrderCreateResultView.swift │ │ │ │ └── PayPalWebTransactionView.swift │ │ └── PayPalWebViewModel │ │ │ ├── PayPalPaymentState.swift │ │ │ └── PayPalWebViewModel.swift │ ├── SwiftUIComponents │ │ ├── CommonComponents │ │ │ ├── CardFormView.swift │ │ │ ├── CircularProgressView.swift │ │ │ ├── CoreConfigManager.swift │ │ │ ├── ErrorView.swift │ │ │ ├── FloatingLabelTextField.swift │ │ │ ├── LabelViewText.swift │ │ │ ├── LeadingText.swift │ │ │ └── RoundedBlueButtonStyle.swift │ │ ├── CurrentState.swift │ │ ├── FeatureSelectionView.swift │ │ └── SwiftUIPaymentButtonDemo.swift │ └── Vault │ │ ├── VaultViewModel │ │ ├── VaultState.swift │ │ └── VaultViewModel.swift │ │ └── VaultViews │ │ ├── CreatePaymentTokenView.swift │ │ ├── CreateSetupTokenView.swift │ │ ├── PaymentTokenResultView.swift │ │ └── SetupTokenResultView.swift ├── DemoUITests │ └── Helpers │ │ ├── LaunchApp.swift │ │ ├── XCUIApplication+Helpers.swift │ │ └── XCUIElement+Helpers.swift └── Settings.bundle │ └── en.lproj │ └── Root.strings ├── Frameworks └── XCFrameworks │ └── PPRiskMagnes.xcframework │ ├── Info.plist │ ├── _CodeSignature │ ├── CodeDirectory │ ├── CodeRequirements │ ├── CodeRequirements-1 │ ├── CodeResources │ └── CodeSignature │ ├── ios-arm64 │ └── PPRiskMagnes.framework │ │ ├── Headers │ │ ├── MagnesCryptoUtil.h │ │ ├── MagnesSystemConfigUtils.h │ │ ├── PPRiskMagnes-Swift.h │ │ └── PPRiskMagnes.h │ │ ├── Info.plist │ │ ├── Modules │ │ ├── PPRiskMagnes.swiftmodule │ │ │ ├── arm64-apple-ios.abi.json │ │ │ ├── arm64-apple-ios.private.swiftinterface │ │ │ ├── arm64-apple-ios.swiftdoc │ │ │ └── arm64-apple-ios.swiftinterface │ │ └── module.modulemap │ │ ├── PPRiskMagnes │ │ └── PrivacyInfo.xcprivacy │ └── ios-arm64_x86_64-simulator │ └── PPRiskMagnes.framework │ ├── Headers │ ├── MagnesCryptoUtil.h │ ├── MagnesSystemConfigUtils.h │ ├── PPRiskMagnes-Swift.h │ └── PPRiskMagnes.h │ ├── Info.plist │ ├── Modules │ ├── PPRiskMagnes.swiftmodule │ │ ├── arm64-apple-ios-simulator.abi.json │ │ ├── arm64-apple-ios-simulator.private.swiftinterface │ │ ├── arm64-apple-ios-simulator.swiftdoc │ │ ├── arm64-apple-ios-simulator.swiftinterface │ │ ├── x86_64-apple-ios-simulator.abi.json │ │ ├── x86_64-apple-ios-simulator.private.swiftinterface │ │ ├── x86_64-apple-ios-simulator.swiftdoc │ │ └── x86_64-apple-ios-simulator.swiftinterface │ └── module.modulemap │ ├── PPRiskMagnes │ ├── PrivacyInfo.xcprivacy │ └── _CodeSignature │ └── CodeResources ├── LICENSE ├── Package.swift ├── PayPal.podspec ├── PayPal.xcodeproj ├── PayPalTests_Info.plist ├── PayPal_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ ├── WorkspaceSettings.xcsettings │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ ├── CardPayments.xcscheme │ ├── CardPaymentsTests.xcscheme │ ├── CorePayments.xcscheme │ ├── CorePaymentsTests.xcscheme │ ├── FraudProtection.xcscheme │ ├── FraudProtectionTests.xcscheme │ ├── PayPalNativePayments.xcscheme │ ├── PayPalNativePaymentsTests.xcscheme │ ├── PayPalWebPayments.xcscheme │ ├── PayPalWebPaymentsTests.xcscheme │ ├── PaymentButtons.xcscheme │ ├── PaymentButtonsTests.xcscheme │ ├── TestShared.xcscheme │ └── UnitTests.xcscheme ├── PayPal.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings ├── README.md ├── Resources └── Fonts │ └── PayPalOpen-Regular.otf ├── SampleApps └── SPMTest │ ├── SPMTest.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── SPMTest │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── ContentView.swift │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ └── SPMTestApp.swift ├── Sources ├── CardPayments │ ├── APIRequests │ │ ├── CheckoutOrdersAPI.swift │ │ ├── ConfirmPaymentSourceRequest.swift │ │ ├── ConfirmPaymentSourceResponse.swift │ │ ├── UpdateSetupTokenResponse.swift │ │ ├── UpdateVaultVariables.swift │ │ └── VaultPaymentTokensAPI.swift │ ├── CardClient.swift │ ├── CardError.swift │ ├── Models │ │ ├── Address.swift │ │ ├── Card.swift │ │ ├── CardRequest.swift │ │ ├── CardResult.swift │ │ ├── CardVaultRequest.swift │ │ ├── CardVaultResult.swift │ │ ├── PaymentSource.swift │ │ └── SCA.swift │ └── PrivacyInfo.xcprivacy ├── CorePayments │ ├── AnalyticsEventData.swift │ ├── CoreConfig.swift │ ├── CorePaymentsError.swift │ ├── CoreSDKError.swift │ ├── Networking │ │ ├── AnalyticsService.swift │ │ ├── Enums │ │ │ ├── Environment.swift │ │ │ ├── HTTPHeader.swift │ │ │ └── HTTPMethod.swift │ │ ├── GraphQL │ │ │ ├── GraphQLErrorResponse.swift │ │ │ ├── GraphQLHTTPPostBody.swift │ │ │ ├── GraphQLHTTPResponse.swift │ │ │ └── GraphQLRequest.swift │ │ ├── HTTP.swift │ │ ├── HTTPRequest.swift │ │ ├── HTTPResponse.swift │ │ ├── HTTPResponseParser.swift │ │ ├── Models │ │ │ └── ErrorResponse.swift │ │ ├── NetworkingClient.swift │ │ ├── NetworkingError.swift │ │ ├── RESTRequest.swift │ │ ├── TrackingEventsAPI.swift │ │ ├── URLSession+URLSessionProtocol.swift │ │ └── URLSessionProtocol.swift │ ├── PayPalCoreConstants.swift │ ├── PrivacyInfo.xcprivacy │ └── WebAuthenticationSession.swift ├── FraudProtection │ ├── CoreConfig+MagnesSDK.swift │ ├── DeviceInspector.swift │ ├── DeviceInspectorProtocol.swift │ ├── MagnesSDKProtocol.swift │ ├── MagnesSDKResult.swift │ ├── MagnesSetupParams.swift │ ├── PayPalDataCollector.swift │ └── PrivacyInfo.xcprivacy ├── PayPalWebPayments │ ├── Environment+PayPalWebCheckout.swift │ ├── PayPalError.swift │ ├── PayPalVaultRequest.swift │ ├── PayPalVaultResult.swift │ ├── PayPalWebCheckoutClient.swift │ ├── PayPalWebCheckoutFundingSource.swift │ ├── PayPalWebCheckoutRequest.swift │ ├── PayPalWebCheckoutResult.swift │ └── PrivacyInfo.xcprivacy └── PaymentButtons │ ├── Assets.xcassets │ ├── Contents.json │ └── Logos │ │ ├── Contents.json │ │ ├── credit_color.imageset │ │ ├── Contents.json │ │ ├── paypal-credit-color.png │ │ ├── paypal-credit-color@2x.png │ │ └── paypal-credit-color@3x.png │ │ ├── credit_monochrome.imageset │ │ ├── Contents.json │ │ ├── paypal-credit-monochrome.png │ │ ├── paypal-credit-monochrome@2x.png │ │ └── paypal-credit-monochrome@3x.png │ │ ├── paypal_blue.imageset │ │ ├── Contents.json │ │ ├── paypal_blue@2x.png │ │ └── paypal_blue@3x.png │ │ ├── paypal_color.imageset │ │ ├── Contents.json │ │ ├── paypal_color@2x-1.png │ │ ├── paypal_color@2x.png │ │ └── paypal_color@3x.png │ │ ├── paypal_monochrome.imageset │ │ ├── Contents.json │ │ ├── paypal_monochrome@2x.png │ │ └── paypal_monochrome@3x.png │ │ ├── paypal_monogram_blue.imageset │ │ ├── Contents.json │ │ ├── paypal_blue_monogram@2x.png │ │ └── paypal_blue_monogram@3x.png │ │ ├── paypal_monogram_color.imageset │ │ ├── Contents.json │ │ ├── paypal_color_monogram@2x.png │ │ └── paypal_color_monogram@3x.png │ │ └── paypal_monogram_monochrome.imageset │ │ ├── Contents.json │ │ ├── paypal_black_monogram@2x.png │ │ └── paypal_black_monogram@3x.png │ ├── Coordinator.swift │ ├── PayPalButton.swift │ ├── PayPalCreditButton.swift │ ├── PayPalPayLaterButton.swift │ ├── PaymentButton+ImageAsset.swift │ ├── PaymentButton.swift │ ├── PaymentButtonColor.swift │ ├── PaymentButtonEdges.swift │ ├── PaymentButtonFont.swift │ ├── PaymentButtonFundingSource.swift │ ├── PaymentButtonLabel.swift │ ├── PaymentButtonSize.swift │ └── PrivacyInfo.xcprivacy ├── UnitTests ├── .swiftlint.yml ├── CardPaymentsTests │ ├── CardClient_Tests.swift │ ├── CheckoutOrdersAPI_Tests.swift │ ├── ConfirmPaymentSourceRequest_Tests.swift │ ├── MockCardVaultDelegate.swift │ ├── MockGraphQLClient.swift │ ├── Mocks │ │ ├── FakeConfirmPaymentResponse.swift │ │ ├── FakeUpdateSetupTokenResponse.swift │ │ ├── MockCheckoutOrdersAPI.swift │ │ └── MockVaultPaymentTokensAPI.swift │ └── VaultPaymentTokensAPI_Tests.swift ├── FraudProtectionTests │ ├── DeviceInspector_Tests.swift │ ├── MockDeviceInspector.swift │ ├── MockMagnesSDK.swift │ ├── MockMagnesSDKResult.swift │ └── PayPalDataCollector_Tests.swift ├── PayPalWebPaymentsTests │ ├── Environment+PayPalWebCheckout_Tests.swift │ └── PayPalWebCheckoutClient_Tests.swift ├── PaymentButtonsTests │ ├── Coordinator_Tests.swift │ ├── PayPalButton_Tests.swift │ ├── PayPalCreditButton_Tests.swift │ └── PayPalPayLaterButton_Tests.swift ├── PaymentsCoreTests │ ├── AnalyticsEventData_Tests.swift │ ├── AnalyticsService_Tests.swift │ ├── Environment_Tests.swift │ ├── HTTPResponseParser_Tests.swift │ ├── HTTPResponse_Tests.swift │ ├── HTTP_Tests.swift │ ├── Mocks │ │ └── MockTrackingEventsAPI.swift │ ├── NetworkingClient_Tests.swift │ └── TrackingEventsAPI_Tests.swift └── TestShared │ ├── FailingJSONEncoder.swift │ ├── FakeRequests.swift │ ├── MockHTTP.swift │ ├── MockNetworkingClient.swift │ ├── MockQuededURLSession.swift │ ├── MockURLSession.swift │ ├── MockViewController.swift │ └── MockWebAuthenticationSession.swift ├── scripts └── swiftlint.sh └── v2_MIGRATION_GUIDE.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @paypal/sdks-ios 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve 3 | body: 4 | - type: input 5 | attributes: 6 | label: PayPal SDK Version 7 | placeholder: "e.g. 1.19.0" 8 | validations: 9 | required: true 10 | - type: dropdown 11 | attributes: 12 | label: Environment 13 | options: 14 | - Sandbox 15 | - Live 16 | - Both 17 | validations: 18 | required: true 19 | - type: input 20 | attributes: 21 | label: Xcode Version 22 | placeholder: "e.g. Xcode 14.3" 23 | validations: 24 | required: true 25 | - type: input 26 | attributes: 27 | label: OS Version & Device 28 | placeholder: "e.g iPhone 14 - iOS 16.4" 29 | validations: 30 | required: false 31 | - type: dropdown 32 | attributes: 33 | label: Integration type 34 | options: 35 | - Swfit Package Manager 36 | - CocoaPods 37 | - Other 38 | validations: 39 | required: true 40 | - type: dropdown 41 | attributes: 42 | label: Development Processor 43 | options: 44 | - Intel 45 | - Apple Silicon (M-series chips) 46 | - Both 47 | validations: 48 | required: true 49 | - type: textarea 50 | attributes: 51 | label: Describe the bug 52 | description: Description of what the bug is. Please include as many details as possible. 53 | validations: 54 | required: true 55 | - type: textarea 56 | attributes: 57 | label: To reproduce 58 | description: Steps to reproduce the behavior. 59 | placeholder: | 60 | 1. Go to '...' 61 | 2. Click on '....' 62 | 3. See error 63 | validations: 64 | required: true 65 | - type: textarea 66 | attributes: 67 | label: Expected behavior 68 | description: Description of what you expected to happen. 69 | validations: 70 | required: true 71 | - type: textarea 72 | attributes: 73 | label: Screenshots 74 | description: If applicable, add screenshots to help explain your problem. 75 | placeholder: | 76 | Do not reveal sensitive data. For example, credit card numbers & customer credentials. 77 | validations: 78 | required: false 79 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Developer Support 4 | url: https://www.paypal-support.com/ 5 | about: If you need help troubleshooting your integration, reach out to PayPal Support. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for our SDK 3 | body: 4 | - type: textarea 5 | attributes: 6 | label: Is your feature request related to a problem? Please describe. 7 | description: A clear and concise description of what the problem is. 8 | validations: 9 | required: false 10 | - type: textarea 11 | attributes: 12 | label: Describe the solution you'd like. 13 | description: Description of what you want to happen. 14 | placeholder: | 15 | Follow the [user story](https://en.wikipedia.org/wiki/User_story) format to clearly describe the use case. 16 | validations: 17 | required: false 18 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Reason for changes 2 | 3 | 4 | 5 | ### Summary of changes 6 | 7 | - 8 | 9 | ### Checklist 10 | 11 | - [ ] Added a changelog entry 12 | 13 | ### Authors 14 | > List GitHub usernames for everyone who contributed to this pull request. 15 | 16 | - -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [pull_request] 3 | concurrency: 4 | group: build-${{ github.event.number }} 5 | cancel-in-progress: true 6 | jobs: 7 | cocoapods: 8 | name: CocoaPods 9 | runs-on: macOS-13 10 | steps: 11 | - name: Check out repository 12 | uses: actions/checkout@v3 13 | with: 14 | ref: ${{ github.event.pull_request.head.ref }} 15 | repository: ${{ github.event.pull_request.head.repo.full_name }} 16 | - name: Use Xcode 15.1 17 | run: sudo xcode-select -switch /Applications/Xcode_15.1.app 18 | - name: Run pod lib lint 19 | run: pod lib lint 20 | spm: 21 | name: Swift Package Manager 22 | runs-on: macOS-13 23 | steps: 24 | - name: Check out repository 25 | uses: actions/checkout@v3 26 | with: 27 | ref: ${{ github.event.pull_request.head.ref }} 28 | repository: ${{ github.event.pull_request.head.repo.full_name }} 29 | - name: Use Xcode 15.1 30 | run: sudo xcode-select -switch /Applications/Xcode_15.1.app 31 | - name: Use current branch 32 | run: sed -i '' 's/branch = .*/branch = \"'"${GITHUB_HEAD_REF//\//\/}"'\";/' SampleApps/SPMTest/SPMTest.xcodeproj/project.pbxproj 33 | - name: Run swift package resolve 34 | run: cd SampleApps/SPMTest && swift package resolve 35 | - name: Build & archive SPMTest 36 | run: xcodebuild -project 'SampleApps/SPMTest/SPMTest.xcodeproj' -scheme 'SPMTest' clean build archive CODE_SIGNING_ALLOWED=NO 37 | 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release SDK 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: "Version to release" 7 | required: true 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: macOS-13 12 | steps: 13 | - name: Check out repository 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | - name: Use Xcode 15.1 18 | run: sudo xcode-select -switch /Applications/Xcode_15.1.app 19 | - name: Check for unreleased section in changelog 20 | run: grep "## unreleased" CHANGELOG.md || (echo "::error::No unreleased section found in CHANGELOG" && exit 1) 21 | - name: Set git username and email 22 | run: | 23 | git config user.name paypalsdks 24 | git config user.email sdks@paypal.com 25 | - name: Update version 26 | run: | 27 | today=$(date +'%Y-%m-%d') 28 | sed -i '' 's/## unreleased.*/## '"${{ github.event.inputs.version }}"' ('"$today"')/' CHANGELOG.md 29 | sed -i '' 's/\(s\.version *= *\).*/\1"'"${{ github.event.inputs.version }}"'\"/' PayPal.podspec 30 | sed -i '' 's/payPalSDKVersion: String =.*/payPalSDKVersion: String = "${{ github.event.inputs.version }}"/' Sources/CorePayments/PayPalCoreConstants.swift 31 | plutil -replace CFBundleShortVersionString -string ${{ github.event.inputs.version }} -- 'Demo/Demo/Info.plist' 32 | 33 | git add . 34 | git commit -m 'Bump version to ${{ github.event.inputs.version }}' 35 | git tag ${{ github.event.inputs.version }} -a -m 'Release ${{ github.event.inputs.version }}' 36 | - name: Push commits and tag 37 | run: git push origin HEAD ${{ github.event.inputs.version }} 38 | - name: Save changelog entries to a file 39 | run: sed -e '1,/##/d' -e '/##/,$d' CHANGELOG.md > changelog_entries.md 40 | - name: Create GitHub release 41 | id: create_release 42 | uses: actions/create-release@v1 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | with: 46 | tag_name: ${{ github.event.inputs.version }} 47 | release_name: ${{ github.event.inputs.version }} 48 | body_path: changelog_entries.md 49 | draft: false 50 | prerelease: false 51 | - name: Publish to CocoaPods 52 | env: 53 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} 54 | run: pod trunk push PayPal.podspec 55 | -------------------------------------------------------------------------------- /.github/workflows/swiftlint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: [pull_request, workflow_dispatch] 3 | concurrency: 4 | group: lint-${{ github.event.number }} 5 | cancel-in-progress: true 6 | jobs: 7 | swiftlint: 8 | name: SwiftLint 9 | runs-on: macOS-latest 10 | steps: 11 | - name: Check out repository 12 | uses: actions/checkout@v2 13 | - name: Use Xcode 15.1 14 | run: sudo xcode-select -switch /Applications/Xcode_15.1.app 15 | - name: Install SwiftLint 16 | run: brew install swiftlint 17 | - name: Run SwiftLint 18 | run: swiftlint --strict 19 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: [pull_request] 3 | concurrency: 4 | group: tests-${{ github.event.number }} 5 | cancel-in-progress: true 6 | jobs: 7 | unit_test_job: 8 | name: Unit 9 | runs-on: macOS-13 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@v3 13 | with: 14 | ref: ${{ github.event.pull_request.head.ref }} 15 | repository: ${{ github.event.pull_request.head.repo.full_name }} 16 | - name: Use Xcode 15.1 17 | run: sudo xcode-select -switch /Applications/Xcode_15.1.app 18 | - name: Run Unit Tests 19 | run: set -o pipefail && xcodebuild -workspace 'PayPal.xcworkspace' -sdk 'iphonesimulator' -configuration 'Debug' -scheme 'UnitTests' -destination 'name=iPhone 14,platform=iOS Simulator' test | xcpretty 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Credentials 5 | Demo/Demo/Credentials.plist 6 | # Xcode 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | profile 19 | *.moved-aside 20 | DerivedData 21 | *.xcscmblueprint 22 | Archives 23 | 24 | # Obj-C/Swift 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | Pods 32 | 33 | # Carthage 34 | Carthage/ 35 | 36 | # SPM 37 | .swiftpm 38 | .build 39 | SampleApps/SPMTest/SPMTest.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved 40 | 41 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # PayPal iOS Development Notes 2 | 3 | This document outlines development practices that we follow while developing this SDK. 4 | 5 | ## SwiftLint 6 | 7 | Ensure that you have [SwiftLint](https://github.com/realm/SwiftLint) installed as we utilize it within our project. 8 | 9 | To install via [Homebrew](https://brew.sh/) run: 10 | ``` 11 | brew install swiftlint 12 | ``` 13 | Our Xcode workspace has a `Run Phase` which integrates in `SwiftLint` so the only prerequisite is installing via `Homebrew`. 14 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "payments-sdk-app-icon.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/payments-sdk-app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Demo/Demo/Assets.xcassets/AppIcon.appiconset/payments-sdk-app-icon.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo/Card/CardType.swift: -------------------------------------------------------------------------------- 1 | enum CardType { 2 | case americanExpress, visa, discover, masterCard, unknown 3 | 4 | var spaceDelimiterIndices: [Int] { 5 | switch self { 6 | case .americanExpress: 7 | return [4, 11] 8 | case .visa: 9 | return [4, 9, 14] 10 | case .discover: 11 | return [4, 9, 14] 12 | case .masterCard: 13 | return [4, 9, 14] 14 | case .unknown: 15 | return [4, 9, 14] 16 | } 17 | } 18 | 19 | var maxLength: Int { 20 | switch self { 21 | case .americanExpress: 22 | return 15 23 | case .visa: 24 | return 16 25 | case .discover: 26 | return 19 27 | case .masterCard: 28 | return 16 29 | case .unknown: 30 | return 16 31 | } 32 | } 33 | 34 | func getCardType(_ cardNumber: String) -> CardType { 35 | if cardNumber.starts(with: "34") || cardNumber.starts(with: "37") { 36 | return .americanExpress 37 | } else if cardNumber.starts(with: "4") { 38 | return .visa 39 | } else if cardNumber.starts(with: "6011") || cardNumber.starts(with: "65") { 40 | return .discover 41 | } else if cardNumber.starts(with: "51") || cardNumber.starts(with: "52") 42 | || cardNumber.starts(with: "53") || cardNumber.starts(with: "54") || cardNumber.starts(with: "55") { 43 | return .masterCard 44 | } else { 45 | return .unknown 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Demo/Demo/CardPayments/CardPaymentViewModel/CardPaymentState.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CardPayments 3 | 4 | struct CardPaymentState: Equatable { 5 | 6 | struct CardResult: Decodable, Equatable { 7 | 8 | let id: String 9 | let status: String? 10 | let didAttemptThreeDSecureAuthentication: Bool 11 | } 12 | 13 | var createOrder: Order? 14 | var authorizedOrder: Order? 15 | var capturedOrder: Order? 16 | var intent: Intent = .authorize 17 | var scaSelection: SCA = .scaWhenRequired 18 | var approveResult: CardResult? 19 | 20 | var createdOrderResponse: LoadingState = .idle { 21 | didSet { 22 | if case .loaded(let value) = createdOrderResponse { 23 | createOrder = value 24 | } 25 | } 26 | } 27 | 28 | var approveResultResponse: LoadingState = .idle { 29 | didSet { 30 | if case .loaded(let value) = approveResultResponse { 31 | approveResult = value 32 | } 33 | } 34 | } 35 | 36 | var capturedOrderResponse: LoadingState = .idle { 37 | didSet { 38 | if case .loaded(let value) = capturedOrderResponse { 39 | capturedOrder = value 40 | } 41 | } 42 | } 43 | 44 | var authorizedOrderResponse: LoadingState = .idle { 45 | didSet { 46 | if case .loaded(let value) = authorizedOrderResponse { 47 | authorizedOrder = value 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Demo/Demo/CardPayments/CardViewComponents/CardOrderActionButton.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CardOrderActionButton: View { 4 | 5 | let intent: Intent 6 | let orderID: String 7 | let selectedMerchantIntegration: MerchantIntegration 8 | 9 | @ObservedObject var cardPaymentViewModel: CardPaymentViewModel 10 | 11 | var body: some View { 12 | ZStack { 13 | Button("\(intent.rawValue)") { 14 | completeOrder() 15 | } 16 | .buttonStyle(RoundedBlueButtonStyle()) 17 | .padding() 18 | 19 | if .loading == cardPaymentViewModel.state.authorizedOrderResponse || 20 | .loading == cardPaymentViewModel.state.capturedOrderResponse { 21 | CircularProgressView() 22 | } 23 | } 24 | } 25 | 26 | private func completeOrder() { 27 | if intent == .capture { 28 | Task { 29 | do { 30 | try await cardPaymentViewModel.captureOrder( 31 | orderID: orderID, 32 | selectedMerchantIntegration: selectedMerchantIntegration 33 | ) 34 | print("Order Captured. ID: \(cardPaymentViewModel.state.capturedOrder?.id ?? "")") 35 | } catch { 36 | print("Error capturing order: \(error.localizedDescription)") 37 | } 38 | } 39 | } else { 40 | Task { 41 | do { 42 | try await cardPaymentViewModel.authorizeOrder( 43 | orderID: orderID, 44 | selectedMerchantIntegration: selectedMerchantIntegration 45 | ) 46 | print("Order Authorized. ID: \(cardPaymentViewModel.state.authorizedOrder?.id ?? "")") 47 | } catch { 48 | print("Error authorizing order: \(error.localizedDescription)") 49 | } 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Demo/Demo/CardPayments/CardViewComponents/CardPaymentOrderCompletionView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CardPaymentOrderCompletionView: View { 4 | 5 | let orderID: String 6 | @ObservedObject var cardPaymentViewModel: CardPaymentViewModel 7 | 8 | var body: some View { 9 | let state = cardPaymentViewModel.state 10 | ScrollView { 11 | ScrollViewReader { scrollView in 12 | VStack { 13 | CardApprovalResultView(cardPaymentViewModel: cardPaymentViewModel) 14 | if state.approveResult != nil { 15 | CardOrderActionButton( 16 | intent: state.intent, 17 | orderID: orderID, 18 | selectedMerchantIntegration: DemoSettings.merchantIntegration, 19 | cardPaymentViewModel: cardPaymentViewModel 20 | ) 21 | } 22 | 23 | if state.authorizedOrder != nil || state.capturedOrder != nil { 24 | CardOrderCompletionResultView(cardPaymentViewModel: cardPaymentViewModel) 25 | } 26 | Text("") 27 | .id("bottomView") 28 | Spacer() 29 | } 30 | .onChange(of: state) { _ in 31 | withAnimation { 32 | scrollView.scrollTo("bottomView") 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Demo/Demo/CardPayments/CardViewComponents/CardPaymentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CardPaymentView: View { 4 | 5 | @StateObject var cardPaymentViewModel = CardPaymentViewModel() 6 | 7 | var body: some View { 8 | ScrollView { 9 | VStack(spacing: 16) { 10 | CreateOrderCardPaymentView( 11 | cardPaymentViewModel: cardPaymentViewModel, 12 | selectedMerchantIntegration: DemoSettings.merchantIntegration 13 | ) 14 | 15 | if let order = cardPaymentViewModel.state.createOrder { 16 | OrderCreateCardResultView(cardPaymentViewModel: cardPaymentViewModel) 17 | NavigationLink { 18 | CardOrderApproveView(orderID: order.id, cardPaymentViewModel: cardPaymentViewModel) 19 | } label: { 20 | Text("Approve Order with Card") 21 | } 22 | .buttonStyle(RoundedBlueButtonStyle()) 23 | .padding() 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Demo/Demo/CardPayments/CardViewComponents/CardPaymentsResultViews/CardApprovalResultView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CardApprovalResultView: View { 4 | 5 | @ObservedObject var cardPaymentViewModel: CardPaymentViewModel 6 | 7 | var body: some View { 8 | switch cardPaymentViewModel.state.approveResultResponse { 9 | case .idle, .loading: 10 | EmptyView() 11 | case .loaded(let approvalResult): 12 | getSuccessView(approvalResult: approvalResult) 13 | case .error(let errorMessage): 14 | ErrorView(errorMessage: errorMessage) 15 | } 16 | } 17 | 18 | func getSuccessView(approvalResult: CardPaymentState.CardResult) -> some View { 19 | VStack(spacing: 16) { 20 | HStack { 21 | Text("Card Approval Result") 22 | .font(.system(size: 20)) 23 | Spacer() 24 | } 25 | LeadingText("ID", weight: .bold) 26 | LeadingText("\(approvalResult.id)") 27 | if let status = approvalResult.status { 28 | LeadingText("Order Status", weight: .bold) 29 | LeadingText("\(status)") 30 | } 31 | LeadingText("didAttemptThreeDSecureAuthentication", weight: .bold) 32 | LeadingText("\(approvalResult.didAttemptThreeDSecureAuthentication)") 33 | } 34 | .frame(maxWidth: .infinity) 35 | .padding() 36 | .background( 37 | RoundedRectangle(cornerRadius: 10) 38 | .stroke(.gray, lineWidth: 2) 39 | .padding(5) 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Demo/Demo/CardPayments/CardViewComponents/CardPaymentsResultViews/OrderCreateCardResultView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct OrderCreateCardResultView: View { 4 | 5 | @ObservedObject var cardPaymentViewModel: CardPaymentViewModel 6 | 7 | var body: some View { 8 | switch cardPaymentViewModel.state.createdOrderResponse { 9 | case .idle, .loading: 10 | EmptyView() 11 | case .loaded(let createOrderResponse): 12 | getSuccessView(createOrderResponse: createOrderResponse) 13 | case .error(let errorMessage): 14 | ErrorView(errorMessage: errorMessage) 15 | } 16 | } 17 | 18 | func getSuccessView(createOrderResponse: Order) -> some View { 19 | VStack(spacing: 16) { 20 | HStack { 21 | Text("Order") 22 | .font(.system(size: 20)) 23 | Spacer() 24 | } 25 | LeadingText("Order ID", weight: .bold) 26 | LeadingText("\(createOrderResponse.id)") 27 | LeadingText("Status", weight: .bold) 28 | LeadingText("\(createOrderResponse.status)") 29 | } 30 | .frame(maxWidth: .infinity) 31 | .padding() 32 | .background( 33 | RoundedRectangle(cornerRadius: 10) 34 | .stroke(.gray, lineWidth: 2) 35 | .padding(5) 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Demo/Demo/CardVault/CardVaultViews/UpdateSetupTokenResultView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct UpdateSetupTokenResultView: View { 4 | 5 | @ObservedObject var cardVaultViewModel: CardVaultViewModel 6 | 7 | var body: some View { 8 | switch cardVaultViewModel.state.updateSetupTokenResponse { 9 | case .idle, .loading: 10 | EmptyView() 11 | case .loaded(let updateSetupTokenResponse): 12 | getSuccessView(updateSetupTokenResponse: updateSetupTokenResponse) 13 | case .error(let errorMessage): 14 | ErrorView(errorMessage: errorMessage) 15 | } 16 | } 17 | 18 | func getSuccessView(updateSetupTokenResponse: UpdateSetupTokenResult) -> some View { 19 | VStack(spacing: 16) { 20 | HStack { 21 | Text("Vault Success") 22 | .font(.system(size: 20)) 23 | Spacer() 24 | } 25 | LeadingText("ID", weight: .bold) 26 | LeadingText("\(updateSetupTokenResponse.id)") 27 | if let status = updateSetupTokenResponse.status { 28 | LeadingText("status", weight: .bold) 29 | LeadingText("\(status)") 30 | } 31 | 32 | LeadingText("didAttemptThreeDSecureAuthentication", weight: .bold) 33 | LeadingText("\(updateSetupTokenResponse.didAttemptThreeDSecureAuthentication)") 34 | } 35 | .frame(maxWidth: .infinity) 36 | .padding() 37 | .background( 38 | RoundedRectangle(cornerRadius: 10) 39 | .stroke(.gray, lineWidth: 2) 40 | .padding(5) 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Demo/Demo/Demo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.associated-domains 6 | 7 | applinks:mobile-sdk-demo-site-838cead5d3ab.herokuapp.com 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Demo/Demo/DemoApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct DemoApp: App { 5 | 6 | var body: some Scene { 7 | WindowGroup { 8 | FeatureSelectionView() 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Demo/DemoSettings/DemoSettings.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum DemoSettings { 4 | 5 | private static let EnvironmentDefaultsKey = "environment" 6 | private static let ClientIDKey = "clientID" 7 | private static let MerchantIntegrationDefaultKey = "merchantIntegration" 8 | 9 | static var environment: Environment { 10 | get { 11 | UserDefaults.standard.string(forKey: EnvironmentDefaultsKey) 12 | .flatMap { Environment(rawValue: $0) } ?? .sandbox 13 | } 14 | set { 15 | UserDefaults.standard.set(newValue.rawValue, forKey: EnvironmentDefaultsKey) 16 | } 17 | } 18 | 19 | static var merchantIntegration: MerchantIntegration { 20 | get { 21 | UserDefaults.standard.string(forKey: MerchantIntegrationDefaultKey) 22 | .flatMap { MerchantIntegration(rawValue: $0) } ?? .direct 23 | } 24 | set { 25 | UserDefaults.standard.set(newValue.rawValue, forKey: MerchantIntegrationDefaultKey) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Demo/Demo/DemoSettings/Environment.swift: -------------------------------------------------------------------------------- 1 | import CorePayments 2 | 3 | enum Environment: String, CaseIterable { 4 | case sandbox 5 | case live 6 | 7 | var baseURL: String { 8 | switch self { 9 | case .sandbox: 10 | return "https://sdk-sample-merchant-server.herokuapp.com" 11 | case .live: 12 | return "https://sdk-sample-merchant-server.herokuapp.com" 13 | } 14 | } 15 | 16 | var paypalSDKEnvironment: CorePayments.Environment { 17 | switch self { 18 | case .sandbox: 19 | return .sandbox 20 | case .live: 21 | return .live 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Demo/Demo/DemoSettings/Intent.swift: -------------------------------------------------------------------------------- 1 | enum Intent: String, CaseIterable, Identifiable { 2 | case authorize = "AUTHORIZE" 3 | case capture = "CAPTURE" 4 | var id: Self { self } 5 | } 6 | -------------------------------------------------------------------------------- /Demo/Demo/Extensions/CardExtensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CardPayments 3 | 4 | extension Card { 5 | 6 | static func createCard(cardNumber: String, expirationDate: String, cvv: String) -> Card { 7 | let cleanedCardText = cardNumber.replacingOccurrences(of: " ", with: "") 8 | 9 | let expirationComponents = expirationDate.components(separatedBy: " / ") 10 | let expirationMonth = expirationComponents[0] 11 | let expirationYear = "20" + expirationComponents[1] 12 | 13 | return Card(number: cleanedCardText, expirationMonth: expirationMonth, expirationYear: expirationYear, securityCode: cvv) 14 | } 15 | 16 | static func isCardFormValid(cardNumber: String, expirationDate: String, cvv: String) -> Bool { 17 | let cleanedCardNumber = cardNumber.replacingOccurrences(of: " ", with: "") 18 | let cleanedExpirationDate = expirationDate.replacingOccurrences(of: " / ", with: "") 19 | 20 | let enabled = cleanedCardNumber.count >= 15 && cleanedCardNumber.count <= 19 21 | && cleanedExpirationDate.count == 4 && cvv.count >= 3 && cvv.count <= 4 22 | return enabled 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Demo/Demo/Extensions/PaymentButtonEnums+Extension.swift: -------------------------------------------------------------------------------- 1 | import PaymentButtons 2 | 3 | extension PayPalPayLaterButton.Color { 4 | 5 | public static var allCases: [PayPalPayLaterButton.Color] { 6 | [.gold, .white, .black, .silver, .blue] 7 | } 8 | 9 | static func allCasesAsString() -> [String] { 10 | Self.allCases.map { $0.rawValue } 11 | } 12 | } 13 | 14 | extension PayPalButton.Color { 15 | 16 | public static var allCases: [PayPalButton.Color] { 17 | [.gold, .white, .black, .silver, .blue] 18 | } 19 | 20 | static func allCasesAsString() -> [String] { 21 | Self.allCases.map { $0.rawValue } 22 | } 23 | } 24 | 25 | extension PayPalCreditButton.Color { 26 | 27 | public static var allCases: [PayPalCreditButton.Color] { 28 | [.white, .black, .darkBlue] 29 | } 30 | 31 | static func allCasesAsString() -> [String] { 32 | Self.allCases.map { $0.rawValue } 33 | } 34 | } 35 | 36 | extension PaymentButtonEdges { 37 | 38 | public static var allCases: [PaymentButtonEdges] { 39 | [.hardEdges, .softEdges, .rounded, .custom(10)] 40 | } 41 | 42 | static func allCasesAsString() -> [String] { 43 | Self.allCases.map { $0.description } 44 | } 45 | } 46 | 47 | extension PaymentButtonSize { 48 | 49 | public static var allCases: [PaymentButtonSize] { 50 | [.mini, .collapsed, .expanded, .full] 51 | } 52 | 53 | static func allCasesAsString() -> [String] { 54 | Self.allCases.map { $0.description } 55 | } 56 | } 57 | 58 | extension PaymentButtonFundingSource { 59 | 60 | public static var allCases: [PaymentButtonFundingSource] { 61 | [.payPal, .payLater, .credit] 62 | } 63 | 64 | static func allCasesAsString() -> [String] { 65 | Self.allCases.map { $0.rawValue } 66 | } 67 | } 68 | 69 | extension PayPalButton.Label { 70 | 71 | public static var allCases: [PayPalButton.Label] { 72 | [.none, .checkout, .buyNow, .payWith] 73 | } 74 | 75 | static func allCasesAsString() -> [String] { 76 | Self.allCases.map { $0.rawValue } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Demo/Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleShortVersionString 6 | 2.0.0 7 | UIApplicationSceneManifest 8 | 9 | UIApplicationSupportsMultipleScenes 10 | 11 | UISceneConfigurations 12 | 13 | UIWindowSceneSessionRoleApplication 14 | 15 | 16 | UISceneConfigurationName 17 | Default Configuration 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Demo/Demo/Models/ApprovalResult.swift: -------------------------------------------------------------------------------- 1 | struct ApprovalResult { 2 | 3 | let orderId: String 4 | let payerId: String 5 | } 6 | -------------------------------------------------------------------------------- /Demo/Demo/Models/ClientIDRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CorePayments 3 | 4 | struct ClientIDRequest { 5 | 6 | typealias ResponseType = ClientIDResponse 7 | 8 | var path: String = "/client_id" 9 | var method: HTTPMethod = .get 10 | var body: Data? 11 | 12 | var headers: [HTTPHeader: String] { 13 | [ 14 | .accept: "application/json", 15 | .acceptLanguage: "en_US" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demo/Demo/Models/ClientIDResponse.swift: -------------------------------------------------------------------------------- 1 | struct ClientIDResponse: Codable { 2 | 3 | enum CodingKeys: String, CodingKey { 4 | case clientID = "value" 5 | } 6 | 7 | let clientID: String 8 | } 9 | -------------------------------------------------------------------------------- /Demo/Demo/Models/CreateSetupTokenParam.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct CreateSetupTokenParam: Encodable { 4 | 5 | let customer: VaultCustomer? 6 | let paymentSource: PaymentSourceType 7 | 8 | enum CodingKeys: String, CodingKey { 9 | case paymentSource = "payment_source" 10 | case customer 11 | } 12 | } 13 | 14 | struct VaultExperienceContext: Encodable { 15 | 16 | let returnUrl = "sdk.ios.paypal://vault/success" 17 | let cancelUrl = "sdk.ios.paypal://vault/cancel" 18 | 19 | enum CodingKeys: String, CodingKey { 20 | case returnUrl = "return_url" 21 | case cancelUrl = "cancel_url" 22 | } 23 | } 24 | 25 | struct PayPal: Encodable { 26 | 27 | var usageType: String 28 | let experienceContext = VaultExperienceContext() 29 | 30 | enum CodingKeys: String, CodingKey { 31 | case usageType = "usage_type" 32 | case experienceContext = "experience_context" 33 | } 34 | } 35 | 36 | struct SetupTokenCard: Encodable { 37 | 38 | let experienceContext = VaultExperienceContext() 39 | let verificationMethod: String? 40 | 41 | enum CodingKeys: String, CodingKey { 42 | case verificationMethod = "verification_method" 43 | case experienceContext = "experience_context" 44 | } 45 | } 46 | 47 | enum PaymentSourceType: Encodable { 48 | case card(verification: String?) 49 | case paypal(usageType: String) 50 | 51 | private enum CodingKeys: String, CodingKey { 52 | case card, paypal 53 | } 54 | 55 | func encode(to encoder: Encoder) throws { 56 | var container = encoder.container(keyedBy: CodingKeys.self) 57 | switch self { 58 | case .card(let verification): 59 | try container.encode(SetupTokenCard(verificationMethod: verification), forKey: .card) 60 | case .paypal(let usageType): 61 | try container.encode(PayPal(usageType: usageType), forKey: .paypal) 62 | } 63 | } 64 | } 65 | 66 | struct VaultCustomer: Encodable { 67 | 68 | var id: String? 69 | 70 | private enum CodingKeys: String, CodingKey { 71 | case id 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Demo/Demo/Models/CreateSetupTokenResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct CreateSetupTokenResponse: Decodable, Equatable { 4 | 5 | static func == (lhs: CreateSetupTokenResponse, rhs: CreateSetupTokenResponse) -> Bool { 6 | lhs.id == rhs.id 7 | } 8 | 9 | let id, status: String 10 | let customer: Customer? 11 | 12 | struct Customer: Decodable { 13 | 14 | let id: String 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Demo/Demo/Models/EmptyBodyParams.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct EmptyBodyParams: Codable {} 4 | -------------------------------------------------------------------------------- /Demo/Demo/Models/Order.swift: -------------------------------------------------------------------------------- 1 | struct Order: Codable, Equatable { 2 | 3 | let id: String 4 | let status: String 5 | let paymentSource: PaymentSource? 6 | 7 | struct PaymentSource: Codable, Equatable { 8 | 9 | let card: Card? 10 | let paypal: PayPal? 11 | } 12 | 13 | init(id: String, status: String, paymentSource: PaymentSource? = nil) { 14 | self.id = id 15 | self.status = status 16 | self.paymentSource = paymentSource 17 | } 18 | 19 | struct Card: Codable, Equatable { 20 | 21 | let lastDigits: String? 22 | let brand: String? 23 | let attributes: Attributes? 24 | } 25 | 26 | struct PayPal: Codable, Equatable { 27 | 28 | let emailAddress: String? 29 | let attributes: Attributes? 30 | } 31 | 32 | struct Attributes: Codable, Equatable { 33 | 34 | let vault: Vault 35 | } 36 | 37 | struct Vault: Codable, Equatable { 38 | 39 | let id: String? 40 | let status: String 41 | let customer: Customer? 42 | } 43 | 44 | struct Customer: Codable, Equatable { 45 | 46 | let id: String 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Demo/Demo/Models/PaymentTokenParam.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct PaymentTokenParam: Encodable { 4 | 5 | let paymentSource: PaymentSource 6 | 7 | struct PaymentSource: Encodable { 8 | 9 | let token: Token 10 | 11 | init(setupTokenID: String) { 12 | token = Token(id: setupTokenID) 13 | } 14 | } 15 | 16 | struct Token: Encodable { 17 | 18 | let id: String 19 | let type = "SETUP_TOKEN" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Demo/Demo/Models/PaymentTokenResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct PaymentTokenResponse: Decodable, Equatable { 4 | 5 | let id: String 6 | let customer: Customer 7 | let paymentSource: PaymentSource 8 | } 9 | 10 | struct Customer: Codable, Equatable { 11 | 12 | let id: String 13 | } 14 | 15 | struct PaymentSource: Decodable, Equatable { 16 | 17 | var card: CardPaymentSource? 18 | var paypal: PayPalPaymentSource? 19 | } 20 | 21 | struct CardPaymentSource: Decodable, Equatable { 22 | 23 | let brand: String? 24 | let lastDigits: String 25 | let expiry: String 26 | } 27 | 28 | struct PayPalPaymentSource: Decodable, Equatable { 29 | 30 | let emailAddress: String 31 | } 32 | -------------------------------------------------------------------------------- /Demo/Demo/Networking/MerchantIntegration.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum MerchantIntegration: String, CaseIterable { 4 | case direct 5 | case connectedPath 6 | case managedPath 7 | 8 | var path: String { 9 | switch self { 10 | case .direct: 11 | return "/direct" 12 | case .connectedPath: 13 | return "/connected_path" 14 | case .managedPath: 15 | return "/managed_path" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demo/Demo/Networking/URLResponseError.swift: -------------------------------------------------------------------------------- 1 | enum URLResponseError: Error { 2 | case dataParsingError 3 | case networkConnectionError 4 | case serverError 5 | case invalidURL 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo/PayPalVault/PayPalVaultViewModel/PayPalVaultViewModel.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PayPalWebPayments 3 | import CorePayments 4 | 5 | class PayPalVaultViewModel: VaultViewModel { 6 | 7 | let configManager = CoreConfigManager(domain: "PayPal Vault") 8 | 9 | func vault(setupTokenID: String) async { 10 | DispatchQueue.main.async { 11 | self.state.paypalVaultTokenResponse = .loading 12 | } 13 | do { 14 | let config = try await configManager.getCoreConfig() 15 | let paypalClient = PayPalWebCheckoutClient(config: config) 16 | let vaultRequest = PayPalVaultRequest(setupTokenID: setupTokenID) 17 | paypalClient.vault(vaultRequest) { result in 18 | switch result { 19 | case .success(let cardVaultResult): 20 | DispatchQueue.main.async { 21 | self.state.paypalVaultTokenResponse = .loaded(cardVaultResult) 22 | } 23 | case .failure(let error): 24 | if error == PayPalError.vaultCanceledError { 25 | DispatchQueue.main.async { 26 | print("Canceled") 27 | self.state.paypalVaultTokenResponse = .idle 28 | } 29 | } else { 30 | DispatchQueue.main.async { 31 | self.state.paypalVaultTokenResponse = .error(message: error.localizedDescription) 32 | } 33 | } 34 | } 35 | } 36 | } catch { 37 | print("Error in vaulting PayPal Payment") 38 | DispatchQueue.main.async { 39 | self.state.paypalVaultTokenResponse = .error(message: error.localizedDescription) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Demo/Demo/PayPalVault/PayPalVaultViews/PayPalVaultResultView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import PayPalWebPayments 3 | 4 | struct PayPalVaultResultView: View { 5 | 6 | @ObservedObject var viewModel: PayPalVaultViewModel 7 | 8 | var body: some View { 9 | switch viewModel.state.paypalVaultTokenResponse { 10 | case .idle, .loading: 11 | EmptyView() 12 | case .loaded(let vaultResult): 13 | getSuccessView(result: vaultResult) 14 | case .error(let errorMessage): 15 | ErrorView(errorMessage: errorMessage) 16 | } 17 | } 18 | 19 | func getSuccessView(result: PayPalVaultResult) -> some View { 20 | VStack(spacing: 16) { 21 | HStack { 22 | Text("Vault Success") 23 | .font(.system(size: 20)) 24 | Spacer() 25 | } 26 | LeadingText("ID", weight: .bold) 27 | LeadingText("\(result.tokenID)") 28 | LeadingText("Status", weight: .bold) 29 | LeadingText("APPROVED") 30 | LeadingText("Approval Session ID", weight: .bold) 31 | LeadingText("\(result.approvalSessionID)") 32 | } 33 | .frame(maxWidth: .infinity) 34 | .padding() 35 | .background( 36 | RoundedRectangle(cornerRadius: 10) 37 | .stroke(.gray, lineWidth: 2) 38 | .padding(5) 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebButtonsView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import PaymentButtons 3 | import PayPalWebPayments 4 | 5 | struct PayPalWebButtonsView: View { 6 | 7 | @ObservedObject var payPalWebViewModel: PayPalWebViewModel 8 | 9 | @State private var selectedFundingSource: PayPalWebCheckoutFundingSource = .paypal 10 | 11 | var body: some View { 12 | VStack { 13 | VStack(alignment: .center, spacing: 16) { 14 | HStack { 15 | Text("Checkout with PayPal") 16 | .font(.system(size: 20)) 17 | Spacer() 18 | } 19 | .frame(maxWidth: .infinity) 20 | .font(.headline) 21 | Picker("Funding Source", selection: $selectedFundingSource) { 22 | Text("PayPal").tag(PayPalWebCheckoutFundingSource.paypal) 23 | Text("PayPal Credit").tag(PayPalWebCheckoutFundingSource.paypalCredit) 24 | Text("Pay Later").tag(PayPalWebCheckoutFundingSource.paylater) 25 | } 26 | .pickerStyle(SegmentedPickerStyle()) 27 | ZStack { 28 | switch selectedFundingSource { 29 | case .paypalCredit: 30 | PayPalCreditButton.Representable(color: .black, size: .full) { 31 | payPalWebViewModel.paymentButtonTapped(funding: .paypalCredit) 32 | } 33 | case .paylater: 34 | PayPalPayLaterButton.Representable(color: .silver, edges: .softEdges, size: .full) { 35 | payPalWebViewModel.paymentButtonTapped(funding: .paylater) 36 | } 37 | case .paypal: 38 | PayPalButton.Representable(color: .blue, size: .full) { 39 | payPalWebViewModel.paymentButtonTapped(funding: .paypal) 40 | } 41 | } 42 | if payPalWebViewModel.state.approveResultResponse == .loading && 43 | payPalWebViewModel.checkoutResult == nil && 44 | payPalWebViewModel.orderID != nil { 45 | CircularProgressView() 46 | } 47 | } 48 | } 49 | .frame(height: 150) 50 | .padding(20) 51 | .background( 52 | RoundedRectangle(cornerRadius: 10) 53 | .stroke(.gray, lineWidth: 2) 54 | .padding(5) 55 | ) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebCreateOrderView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PayPalWebCreateOrderView: View { 4 | 5 | @ObservedObject var payPalWebViewModel: PayPalWebViewModel 6 | 7 | @State private var selectedIntent: Intent = .authorize 8 | @State var shouldVaultSelected = false 9 | 10 | var body: some View { 11 | VStack(spacing: 16) { 12 | HStack { 13 | Text("Create an Order") 14 | .font(.system(size: 20)) 15 | Spacer() 16 | Button("Reset") { 17 | payPalWebViewModel.resetState() 18 | } 19 | } 20 | .frame(maxWidth: .infinity) 21 | .font(.headline) 22 | Picker("Intent", selection: $selectedIntent) { 23 | Text("AUTHORIZE").tag(Intent.authorize) 24 | Text("CAPTURE").tag(Intent.capture) 25 | } 26 | .pickerStyle(SegmentedPickerStyle()) 27 | HStack { 28 | Toggle("Should Vault with Purchase", isOn: $shouldVaultSelected) 29 | Spacer() 30 | } 31 | ZStack { 32 | Button("Create an Order") { 33 | Task { 34 | do { 35 | payPalWebViewModel.intent = selectedIntent 36 | try await payPalWebViewModel.createOrder(shouldVault: shouldVaultSelected) 37 | } catch { 38 | print("Error in getting setup token. \(error.localizedDescription)") 39 | } 40 | } 41 | } 42 | .buttonStyle(RoundedBlueButtonStyle()) 43 | if case .loading = payPalWebViewModel.state.createdOrderResponse { 44 | CircularProgressView() 45 | } 46 | } 47 | } 48 | .padding() 49 | .background( 50 | RoundedRectangle(cornerRadius: 10) 51 | .stroke(.gray, lineWidth: 2) 52 | .padding(5) 53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebPaymentsView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PayPalWebPaymentsView: View { 4 | 5 | @StateObject var payPalWebViewModel = PayPalWebViewModel() 6 | 7 | var body: some View { 8 | ScrollView { 9 | ScrollViewReader { scrollView in 10 | VStack(spacing: 16) { 11 | 12 | PayPalWebCreateOrderView(payPalWebViewModel: payPalWebViewModel) 13 | 14 | if case .loaded = payPalWebViewModel.state.createdOrderResponse { 15 | PayPalOrderCreateResultView(payPalWebViewModel: payPalWebViewModel) 16 | 17 | PayPalWebButtonsView(payPalWebViewModel: payPalWebViewModel) 18 | } 19 | 20 | if case .loaded = payPalWebViewModel.state.approveResultResponse { 21 | PayPalApprovalResultView(payPalWebViewModel: payPalWebViewModel) 22 | 23 | PayPalWebTransactionView(payPalWebViewModel: payPalWebViewModel) 24 | .padding(.bottom, 20) 25 | } 26 | 27 | if case .loaded = payPalWebViewModel.state.capturedOrderResponse { 28 | PayPalOrderCompletionResultView(payPalWebViewModel: payPalWebViewModel) 29 | } else if case .loaded = payPalWebViewModel.state.authorizedOrderResponse { 30 | PayPalOrderCompletionResultView(payPalWebViewModel: payPalWebViewModel) 31 | } 32 | Text("") 33 | .id("bottomView") 34 | } 35 | .onChange(of: payPalWebViewModel.state) { _ in 36 | withAnimation { 37 | scrollView.scrollTo("bottomView") 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalApprovalResultView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PayPalApprovalResultView: View { 4 | 5 | @ObservedObject var payPalWebViewModel: PayPalWebViewModel 6 | 7 | var body: some View { 8 | switch payPalWebViewModel.state.approveResultResponse { 9 | case .idle, .loading: 10 | EmptyView() 11 | case .error(let message): 12 | ErrorView(errorMessage: message) 13 | case .loaded(let approvalResult): 14 | getApprovalSuccessView(approvalResult: approvalResult) 15 | } 16 | } 17 | 18 | func getApprovalSuccessView(approvalResult: PayPalPaymentState.ApprovalResult) -> some View { 19 | VStack(alignment: .leading, spacing: 16) { 20 | Text("Approval Result") 21 | .font(.headline) 22 | LeadingText("Approval ID", weight: .bold) 23 | .font(.system(size: 20)) 24 | 25 | LeadingText(approvalResult.id) 26 | if let status = approvalResult.status { 27 | LeadingText("Status", weight: .bold) 28 | LeadingText(status) 29 | } 30 | } 31 | .frame(maxWidth: .infinity) 32 | .padding() 33 | .background( 34 | RoundedRectangle(cornerRadius: 10) 35 | .stroke(.gray, lineWidth: 2) 36 | .padding(5) 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalOrderCompletionResultView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PayPalOrderCompletionResultView: View { 4 | 5 | @ObservedObject var payPalWebViewModel: PayPalWebViewModel 6 | 7 | var body: some View { 8 | VStack { 9 | if case .loaded(let authorizedOrder) = payPalWebViewModel.state.authorizedOrderResponse { 10 | getCompletionSuccessView(order: authorizedOrder, intent: "Authorized") 11 | } 12 | if case .loaded(let capturedOrder) = payPalWebViewModel.state.capturedOrderResponse { 13 | getCompletionSuccessView(order: capturedOrder, intent: "Captured") 14 | } 15 | } 16 | } 17 | 18 | func getCompletionSuccessView(order: Order, intent: String) -> some View { 19 | VStack(alignment: .leading, spacing: 16) { 20 | Text("Order \(intent) Successfully") 21 | .font(.system(size: 20)) 22 | 23 | LabelViewText("Order ID:", bodyText: order.id) 24 | 25 | LabelViewText("Status:", bodyText: order.status) 26 | 27 | if let payerID = payPalWebViewModel.checkoutResult?.payerID { 28 | LabelViewText("Payer ID:", bodyText: payerID) 29 | } 30 | 31 | if let emailAddress = order.paymentSource?.paypal?.emailAddress { 32 | LabelViewText("Email:", bodyText: emailAddress) 33 | } 34 | 35 | if let vaultID = order.paymentSource?.paypal?.attributes?.vault.id { 36 | LabelViewText("Payment Token:", bodyText: vaultID) 37 | } 38 | 39 | if let customerID = order.paymentSource?.paypal?.attributes?.vault.customer?.id { 40 | LabelViewText("Customer ID:", bodyText: customerID) 41 | } 42 | } 43 | .frame(maxWidth: .infinity) 44 | .padding() 45 | .background( 46 | RoundedRectangle(cornerRadius: 10) 47 | .stroke(.gray, lineWidth: 2) 48 | .padding(5) 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalOrderCreateResultView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PayPalOrderCreateResultView: View { 4 | 5 | @ObservedObject var payPalWebViewModel: PayPalWebViewModel 6 | 7 | var body: some View { 8 | switch payPalWebViewModel.state.createdOrderResponse { 9 | case .idle, .loading: 10 | EmptyView() 11 | case .loaded(let createOrderResponse): 12 | getSuccessView(createOrderResponse: createOrderResponse) 13 | case .error(let errorMessage): 14 | ErrorView(errorMessage: errorMessage) 15 | } 16 | } 17 | 18 | func getSuccessView(createOrderResponse: Order) -> some View { 19 | VStack(alignment: .leading, spacing: 16) { 20 | HStack { 21 | Text("Order Details") 22 | .font(.system(size: 20)) 23 | Spacer() 24 | } 25 | 26 | LabelViewText("Order ID:", bodyText: createOrderResponse.id) 27 | 28 | LabelViewText("Status:", bodyText: createOrderResponse.status) 29 | 30 | if let payerID = payPalWebViewModel.checkoutResult?.payerID { 31 | LabelViewText("Payer ID:", bodyText: payerID) 32 | } 33 | 34 | if let emailAddress = createOrderResponse.paymentSource?.paypal?.emailAddress { 35 | LabelViewText("Email:", bodyText: emailAddress) 36 | } 37 | 38 | if let vaultID = createOrderResponse.paymentSource?.paypal?.attributes?.vault.id { 39 | LabelViewText("Payment Token:", bodyText: vaultID) 40 | } 41 | 42 | if let customerID = createOrderResponse.paymentSource?.paypal?.attributes?.vault.customer?.id { 43 | LabelViewText("Customer ID:", bodyText: customerID) 44 | } 45 | } 46 | .frame(maxWidth: .infinity) 47 | .padding() 48 | .background( 49 | RoundedRectangle(cornerRadius: 10) 50 | .stroke(.gray, lineWidth: 2) 51 | .padding(5) 52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Demo/Demo/PayPalWebPayments/PayPalWebPaymentsView/PayPalWebResultViews/PayPalWebTransactionView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PayPalWebTransactionView: View { 4 | 5 | @ObservedObject var payPalWebViewModel: PayPalWebViewModel 6 | 7 | var body: some View { 8 | VStack { 9 | ZStack { 10 | Button("\(payPalWebViewModel.intent.rawValue.capitalized) Order") { 11 | Task { 12 | do { 13 | try await payPalWebViewModel.completeTransaction() 14 | } catch { 15 | print("Error capturing order: \(error.localizedDescription)") 16 | } 17 | } 18 | } 19 | .buttonStyle(RoundedBlueButtonStyle()) 20 | .padding() 21 | 22 | if payPalWebViewModel.state.capturedOrderResponse == .loading || 23 | payPalWebViewModel.state.authorizedOrderResponse == .loading { 24 | CircularProgressView() 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Demo/Demo/PayPalWebPayments/PayPalWebViewModel/PayPalPaymentState.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PayPalWebPayments 3 | 4 | struct PayPalPaymentState: Equatable { 5 | 6 | struct ApprovalResult: Decodable, Equatable { 7 | 8 | let id: String 9 | let status: String? 10 | } 11 | 12 | var createOrder: Order? 13 | var authorizedOrder: Order? 14 | var capturedOrder: Order? 15 | var intent: Intent = .authorize 16 | var approveResult: ApprovalResult? 17 | 18 | var createdOrderResponse: LoadingState = .idle { 19 | didSet { 20 | if case .loaded(let value) = createdOrderResponse { 21 | createOrder = value 22 | } 23 | } 24 | } 25 | 26 | var approveResultResponse: LoadingState = .idle { 27 | didSet { 28 | if case .loaded(let value) = approveResultResponse { 29 | approveResult = value 30 | } 31 | } 32 | } 33 | 34 | var capturedOrderResponse: LoadingState = .idle { 35 | didSet { 36 | if case .loaded(let value) = capturedOrderResponse { 37 | capturedOrder = value 38 | } 39 | } 40 | } 41 | 42 | var authorizedOrderResponse: LoadingState = .idle { 43 | didSet { 44 | if case .loaded(let value) = authorizedOrderResponse { 45 | authorizedOrder = value 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Demo/Demo/SwiftUIComponents/CommonComponents/CircularProgressView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CircularProgressView: View { 4 | 5 | var body: some View { 6 | ProgressView() 7 | .progressViewStyle(CircularProgressViewStyle(tint: Color.white)) 8 | .background(Color.black.opacity(0.4)) 9 | .cornerRadius(10) 10 | .frame(maxWidth: .infinity) 11 | } 12 | } 13 | 14 | struct CircularProgressView_Previews: PreviewProvider { 15 | 16 | static var previews: some View { 17 | CircularProgressView() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Demo/Demo/SwiftUIComponents/CommonComponents/CoreConfigManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CorePayments 3 | 4 | class CoreConfigManager { 5 | 6 | let domain: String 7 | 8 | public init(domain: String) { 9 | self.domain = domain 10 | } 11 | 12 | func getClientID() async -> String? { 13 | await DemoMerchantAPI.sharedService.getClientID( 14 | environment: DemoSettings.environment, 15 | selectedMerchantIntegration: DemoSettings.merchantIntegration 16 | ) 17 | } 18 | 19 | func getCoreConfig() async throws -> CoreConfig { 20 | guard let clientID = await getClientID() else { 21 | throw CoreSDKError(code: 0, domain: domain, errorDescription: "Error getting clientID") 22 | } 23 | 24 | return CoreConfig(clientID: clientID, environment: DemoSettings.environment.paypalSDKEnvironment) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Demo/Demo/SwiftUIComponents/CommonComponents/ErrorView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ErrorView: View { 4 | 5 | let errorMessage: String 6 | 7 | var body: some View { 8 | VStack { 9 | Text("\(errorMessage)") 10 | .frame(maxWidth: .infinity) 11 | } 12 | .padding() 13 | .background( 14 | RoundedRectangle(cornerRadius: 10) 15 | .stroke(.gray, lineWidth: 2) 16 | .padding(5) 17 | ) 18 | .frame(maxWidth: .infinity) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Demo/Demo/SwiftUIComponents/CommonComponents/FloatingLabelTextField.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct FloatingLabelTextField: View { 4 | 5 | let placeholder: String 6 | @Binding var text: String 7 | 8 | var body: some View { 9 | ZStack(alignment: .leading) { 10 | Text(placeholder) 11 | .offset(y: text.isEmpty ? 0 : -25) 12 | .scaleEffect(text.isEmpty ? 1 : 0.9, anchor: .leading) 13 | .font(.system(text.isEmpty ? .title3 : .title3, design: .rounded)) 14 | .foregroundColor(.gray) 15 | TextField("", text: $text) 16 | .keyboardType(.numberPad) 17 | } 18 | .padding(.top, text.isEmpty ? 0 : 18) 19 | .animation(.default, value: 0) 20 | .padding() 21 | .background( 22 | RoundedRectangle(cornerRadius: 10) 23 | .stroke(text.isEmpty ? .black.opacity(0.5) : .gray, lineWidth: 2) 24 | ) 25 | .cornerRadius(10) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Demo/Demo/SwiftUIComponents/CommonComponents/LabelViewText.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct LabelViewText: View { 4 | 5 | var titleText: String 6 | var bodyText: String 7 | 8 | var body: some View { 9 | HStack { 10 | Text(titleText).fontWeight(.bold) 11 | Text(bodyText) 12 | } 13 | } 14 | 15 | init(_ titleText: String, bodyText: String) { 16 | self.titleText = titleText 17 | self.bodyText = bodyText 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Demo/Demo/SwiftUIComponents/CommonComponents/LeadingText.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct LeadingText: View { 4 | 5 | var text: String 6 | var weight: Font.Weight? 7 | 8 | var body: some View { 9 | Text(text) 10 | .fontWeight(weight) 11 | .frame(maxWidth: .infinity, alignment: .leading) 12 | } 13 | 14 | init(_ text: String, weight: Font.Weight? = nil) { 15 | self.text = text 16 | self.weight = weight 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demo/Demo/SwiftUIComponents/CommonComponents/RoundedBlueButtonStyle.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct RoundedBlueButtonStyle: ButtonStyle { 4 | 5 | func makeBody(configuration: Configuration) -> some View { 6 | configuration.label 7 | .foregroundColor(.white) 8 | .padding() 9 | .frame(maxWidth: .infinity) 10 | .background(.blue) 11 | .cornerRadius(10) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Demo/Demo/SwiftUIComponents/CurrentState.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum CurrentState: Equatable { 4 | case idle 5 | case loading 6 | case success 7 | case error(message: String) 8 | } 9 | -------------------------------------------------------------------------------- /Demo/Demo/Vault/VaultViewModel/VaultState.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CardPayments 3 | import PayPalWebPayments 4 | 5 | struct UpdateSetupTokenResult: Decodable, Equatable { 6 | 7 | var id: String 8 | var status: String? 9 | var didAttemptThreeDSecureAuthentication: Bool 10 | } 11 | 12 | struct VaultState: Equatable { 13 | 14 | var setupToken: CreateSetupTokenResponse? 15 | var paymentToken: PaymentTokenResponse? 16 | // result for card vault 17 | var updateSetupToken: UpdateSetupTokenResult? 18 | // result for paypal vault 19 | var paypalVaultToken: PayPalVaultResult? 20 | 21 | var setupTokenResponse: LoadingState = .idle { 22 | didSet { 23 | if case .loaded(let value) = setupTokenResponse { 24 | setupToken = value 25 | } 26 | } 27 | } 28 | 29 | var paymentTokenResponse: LoadingState = .idle { 30 | didSet { 31 | if case .loaded(let value) = paymentTokenResponse { 32 | paymentToken = value 33 | } 34 | } 35 | } 36 | 37 | // response from Card Vault 38 | var updateSetupTokenResponse: LoadingState = .idle { 39 | didSet { 40 | if case .loaded(let value) = updateSetupTokenResponse { 41 | updateSetupToken = value 42 | } 43 | } 44 | } 45 | 46 | // response from PayPal Vault 47 | var paypalVaultTokenResponse: LoadingState = .idle { 48 | didSet { 49 | if case .loaded(let value) = paypalVaultTokenResponse { 50 | paypalVaultToken = value 51 | } 52 | } 53 | } 54 | } 55 | 56 | enum LoadingState: Equatable { 57 | 58 | case idle 59 | case loading 60 | case error(message: String) 61 | case loaded(_ value: T) 62 | } 63 | -------------------------------------------------------------------------------- /Demo/Demo/Vault/VaultViewModel/VaultViewModel.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | class VaultViewModel: ObservableObject { 4 | 5 | @Published var state = VaultState() 6 | 7 | func getSetupToken( 8 | customerID: String? = nil, 9 | selectedMerchantIntegration: MerchantIntegration, 10 | paymentSourceType: PaymentSourceType 11 | ) async throws { 12 | do { 13 | DispatchQueue.main.async { 14 | self.state.setupTokenResponse = .loading 15 | } 16 | let setupTokenResult = try await DemoMerchantAPI.sharedService.createSetupToken( 17 | customerID: customerID, 18 | selectedMerchantIntegration: selectedMerchantIntegration, 19 | paymentSourceType: paymentSourceType 20 | ) 21 | DispatchQueue.main.async { 22 | self.state.setupTokenResponse = .loaded(setupTokenResult) 23 | } 24 | } catch { 25 | DispatchQueue.main.async { 26 | self.state.setupTokenResponse = .error(message: error.localizedDescription) 27 | } 28 | throw error 29 | } 30 | } 31 | 32 | func resetState() { 33 | state = VaultState() 34 | } 35 | 36 | func getPaymentToken( 37 | setupToken: String, 38 | selectedMerchantIntegration: MerchantIntegration 39 | ) async throws { 40 | do { 41 | DispatchQueue.main.async { 42 | self.state.paymentTokenResponse = .loading 43 | } 44 | let paymentTokenResult = try await DemoMerchantAPI.sharedService.createPaymentToken( 45 | setupToken: setupToken, 46 | selectedMerchantIntegration: selectedMerchantIntegration 47 | ) 48 | DispatchQueue.main.async { 49 | self.state.paymentTokenResponse = .loaded(paymentTokenResult) 50 | } 51 | } catch { 52 | DispatchQueue.main.async { 53 | self.state.paymentTokenResponse = .error(message: error.localizedDescription) 54 | } 55 | throw error 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Demo/Demo/Vault/VaultViews/CreatePaymentTokenView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CreatePaymentTokenView: View { 4 | 5 | let selectedMerchantIntegration: MerchantIntegration 6 | let setupToken: String 7 | 8 | @ObservedObject var vaultViewModel: VaultViewModel 9 | 10 | public init(vaultViewModel: VaultViewModel, selectedMerchantIntegration: MerchantIntegration, setupToken: String) { 11 | self.vaultViewModel = vaultViewModel 12 | self.selectedMerchantIntegration = selectedMerchantIntegration 13 | self.setupToken = setupToken 14 | } 15 | 16 | var body: some View { 17 | VStack(spacing: 16) { 18 | HStack { 19 | Text("Create a Payment Method Token") 20 | .font(.system(size: 20)) 21 | Spacer() 22 | } 23 | .frame(maxWidth: .infinity) 24 | .font(.headline) 25 | ZStack { 26 | Button("Create Payment Token") { 27 | Task { 28 | do { 29 | try await vaultViewModel.getPaymentToken( 30 | setupToken: setupToken, 31 | selectedMerchantIntegration: selectedMerchantIntegration 32 | ) 33 | } catch { 34 | print("Error in getting payment token. \(error.localizedDescription)") 35 | } 36 | } 37 | } 38 | .buttonStyle(RoundedBlueButtonStyle()) 39 | if case .loading = vaultViewModel.state.paymentTokenResponse { 40 | CircularProgressView() 41 | } 42 | } 43 | } 44 | .padding() 45 | .background( 46 | RoundedRectangle(cornerRadius: 10) 47 | .stroke(.gray, lineWidth: 2) 48 | .padding(5) 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Demo/Demo/Vault/VaultViews/PaymentTokenResultView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PaymentTokenResultView: View { 4 | 5 | @ObservedObject var vaultViewModel: VaultViewModel 6 | 7 | var body: some View { 8 | switch vaultViewModel.state.paymentTokenResponse { 9 | case .idle, .loading: 10 | EmptyView() 11 | case .loaded(let paymentTokenResponse): 12 | getSucessView(paymentTokenResponse: paymentTokenResponse) 13 | case .error(let errorMessage): 14 | ErrorView(errorMessage: errorMessage) 15 | } 16 | } 17 | 18 | func getSucessView(paymentTokenResponse: PaymentTokenResponse) -> some View { 19 | VStack(spacing: 16) { 20 | HStack { 21 | Text("Payment Token") 22 | .font(.system(size: 20)) 23 | Spacer() 24 | } 25 | LeadingText("ID", weight: .bold) 26 | LeadingText("\(paymentTokenResponse.id)") 27 | LeadingText("Customer ID", weight: .bold) 28 | LeadingText("\(paymentTokenResponse.customer.id)") 29 | if let card = paymentTokenResponse.paymentSource.card { 30 | LeadingText("Card Brand", weight: .bold) 31 | LeadingText("\(card.brand ?? "")") 32 | LeadingText("Card Last 4", weight: .bold) 33 | LeadingText("\(card.lastDigits)") 34 | } else if let paypal = paymentTokenResponse.paymentSource.paypal { 35 | LeadingText("Email", weight: .bold) 36 | LeadingText("\(paypal.emailAddress)") 37 | } 38 | } 39 | .frame(maxWidth: .infinity) 40 | .padding() 41 | .background( 42 | RoundedRectangle(cornerRadius: 10) 43 | .stroke(.gray, lineWidth: 2) 44 | .padding(5) 45 | ) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Demo/Demo/Vault/VaultViews/SetupTokenResultView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct SetupTokenResultView: View { 4 | 5 | @ObservedObject var vaultViewModel: VaultViewModel 6 | 7 | var body: some View { 8 | switch vaultViewModel.state.setupTokenResponse { 9 | case .idle, .loading: 10 | EmptyView() 11 | case .loaded(let setupTokenResponse): 12 | getSuccessView(setupTokenResponse: setupTokenResponse) 13 | case .error(let errorMessage): 14 | ErrorView(errorMessage: errorMessage) 15 | } 16 | } 17 | 18 | func getSuccessView(setupTokenResponse: CreateSetupTokenResponse) -> some View { 19 | VStack(spacing: 16) { 20 | HStack { 21 | Text("Setup Token") 22 | .font(.system(size: 20)) 23 | Spacer() 24 | } 25 | LeadingText("ID", weight: .bold) 26 | LeadingText("\(setupTokenResponse.id)") 27 | LeadingText("Customer ID", weight: .bold) 28 | LeadingText("\(setupTokenResponse.customer?.id ?? "")") 29 | LeadingText("Status", weight: .bold) 30 | LeadingText("\(setupTokenResponse.status)") 31 | } 32 | .frame(maxWidth: .infinity) 33 | .padding() 34 | .background( 35 | RoundedRectangle(cornerRadius: 10) 36 | .stroke(.gray, lineWidth: 2) 37 | .padding(5) 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Demo/DemoUITests/Helpers/LaunchApp.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | func launchApp(withArgs launchArgs: [String] = []) -> XCUIApplication { 4 | let app = XCUIApplication() 5 | for arg in launchArgs { 6 | app.launchArguments.append(arg) 7 | } 8 | app.launch() 9 | return app 10 | } 11 | -------------------------------------------------------------------------------- /Demo/DemoUITests/Helpers/XCUIApplication+Helpers.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | extension XCUIApplication { 4 | 5 | func button(named buttonName: String) -> XCUIElement { 6 | return buttons[buttonName] 7 | } 8 | 9 | func textField(named textFieldName: String) -> XCUIElement { 10 | return textFields[textFieldName] 11 | } 12 | 13 | func staticText(containing text: String) -> XCUIElement { 14 | // Ref: https://stackoverflow.com/a/47253096 15 | let predicate = NSPredicate(format: "label CONTAINS[c] %@", text) 16 | return staticTexts.containing(predicate).element 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demo/DemoUITests/Helpers/XCUIElement+Helpers.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | private let defaultWaitTime: TimeInterval = 20.0 4 | 5 | extension XCUIElement { 6 | 7 | func waitForExistence() -> Bool { 8 | return waitForExistence(timeout: defaultWaitTime) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Demo/Settings.bundle/en.lproj/Root.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Demo/Settings.bundle/en.lproj/Root.strings -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AvailableLibraries 6 | 7 | 8 | BinaryPath 9 | PPRiskMagnes.framework/PPRiskMagnes 10 | LibraryIdentifier 11 | ios-arm64_x86_64-simulator 12 | LibraryPath 13 | PPRiskMagnes.framework 14 | SupportedArchitectures 15 | 16 | arm64 17 | x86_64 18 | 19 | SupportedPlatform 20 | ios 21 | SupportedPlatformVariant 22 | simulator 23 | 24 | 25 | BinaryPath 26 | PPRiskMagnes.framework/PPRiskMagnes 27 | LibraryIdentifier 28 | ios-arm64 29 | LibraryPath 30 | PPRiskMagnes.framework 31 | SupportedArchitectures 32 | 33 | arm64 34 | 35 | SupportedPlatform 36 | ios 37 | 38 | 39 | CFBundlePackageType 40 | XFWK 41 | XCFrameworkFormatVersion 42 | 1.0 43 | 44 | 45 | -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/_CodeSignature/CodeDirectory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Frameworks/XCFrameworks/PPRiskMagnes.xcframework/_CodeSignature/CodeDirectory -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/_CodeSignature/CodeRequirements: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Frameworks/XCFrameworks/PPRiskMagnes.xcframework/_CodeSignature/CodeRequirements -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/_CodeSignature/CodeRequirements-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Frameworks/XCFrameworks/PPRiskMagnes.xcframework/_CodeSignature/CodeRequirements-1 -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/_CodeSignature/CodeSignature: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Frameworks/XCFrameworks/PPRiskMagnes.xcframework/_CodeSignature/CodeSignature -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64/PPRiskMagnes.framework/Headers/MagnesCryptoUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // MagnesCryptoUtil.h 3 | // PPRiskMagnes 4 | // 5 | // Created by Zhou, James on 5/10/18. 6 | // Copyright © 2018 PayPal Risk. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MagnesCryptoUtil : NSObject 12 | 13 | + (NSString *)getDCIdWithAppGuid:(NSString *)appGuid 14 | withTimestamp:(NSString *)timestamp; 15 | 16 | + (NSString *)getMGIdWithAppGuid:(NSString *)appGuid 17 | withTimeStamp:(NSString *)timestampN 18 | withPairingId:(NSString *)pairingId 19 | withMGIDKey:(NSString *)mgIdKey; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64/PPRiskMagnes.framework/Headers/MagnesSystemConfigUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+MagnesSystemConfigUtils.h 3 | // PPRiskMagnes 4 | // 5 | // Created by Mahalingam, Omkumar on 9/13/18. 6 | // Copyright © 2018 PayPal Risk. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MagnesSystemConfigUtils : NSObject 12 | 13 | + (NSString *) getCPUType; 14 | + (NSString *) getCPUName; 15 | + (NSString *) getHardwareModel; 16 | + (NSString *) getKernelVersion; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64/PPRiskMagnes.framework/Headers/PPRiskMagnes.h: -------------------------------------------------------------------------------- 1 | // 2 | // PPRiskMagnes.h 3 | // PPRiskMagnes 4 | // 5 | // Created by James Zhou on 7/31/17. 6 | // Copyright © 2017 PayPal Risk. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MagnesCryptoUtil.h" 11 | #import "MagnesSystemConfigUtils.h" 12 | //! Project version number for PPRiskMagnes. 13 | FOUNDATION_EXPORT double PPRiskMagnesVersionNumber; 14 | 15 | //! Project version string for PPRiskMagnes. 16 | FOUNDATION_EXPORT const unsigned char PPRiskMagnesVersionString[]; 17 | 18 | // In this header, you should import all the public headers of your framework using statements like #import 19 | -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64/PPRiskMagnes.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64/PPRiskMagnes.framework/Info.plist -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64/PPRiskMagnes.framework/Modules/PPRiskMagnes.swiftmodule/arm64-apple-ios.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64/PPRiskMagnes.framework/Modules/PPRiskMagnes.swiftmodule/arm64-apple-ios.swiftdoc -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64/PPRiskMagnes.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module PPRiskMagnes { 2 | umbrella header "PPRiskMagnes.h" 3 | export * 4 | 5 | module * { export * } 6 | } 7 | 8 | module PPRiskMagnes.Swift { 9 | header "PPRiskMagnes-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64/PPRiskMagnes.framework/PPRiskMagnes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64/PPRiskMagnes.framework/PPRiskMagnes -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64/PPRiskMagnes.framework/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryUserDefaults 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | CA92.1 13 | 14 | 15 | 16 | NSPrivacyCollectedDataTypes 17 | 18 | 19 | NSPrivacyCollectedDataType 20 | NSPrivacyCollectedDataTypePerformanceData 21 | NSPrivacyCollectedDataTypeLinked 22 | 23 | NSPrivacyCollectedDataTypeTracking 24 | 25 | NSPrivacyCollectedDataTypePurposes 26 | 27 | NSPrivacyCollectedDataTypePurposeAppFunctionality 28 | 29 | 30 | 31 | NSPrivacyCollectedDataType 32 | NSPrivacyCollectedDataTypePreciseLocation 33 | NSPrivacyCollectedDataTypeLinked 34 | 35 | NSPrivacyCollectedDataTypeTracking 36 | 37 | NSPrivacyCollectedDataTypePurposes 38 | 39 | NSPrivacyCollectedDataTypePurposeAppFunctionality 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64_x86_64-simulator/PPRiskMagnes.framework/Headers/MagnesCryptoUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // MagnesCryptoUtil.h 3 | // PPRiskMagnes 4 | // 5 | // Created by Zhou, James on 5/10/18. 6 | // Copyright © 2018 PayPal Risk. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MagnesCryptoUtil : NSObject 12 | 13 | + (NSString *)getDCIdWithAppGuid:(NSString *)appGuid 14 | withTimestamp:(NSString *)timestamp; 15 | 16 | + (NSString *)getMGIdWithAppGuid:(NSString *)appGuid 17 | withTimeStamp:(NSString *)timestampN 18 | withPairingId:(NSString *)pairingId 19 | withMGIDKey:(NSString *)mgIdKey; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64_x86_64-simulator/PPRiskMagnes.framework/Headers/MagnesSystemConfigUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+MagnesSystemConfigUtils.h 3 | // PPRiskMagnes 4 | // 5 | // Created by Mahalingam, Omkumar on 9/13/18. 6 | // Copyright © 2018 PayPal Risk. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MagnesSystemConfigUtils : NSObject 12 | 13 | + (NSString *) getCPUType; 14 | + (NSString *) getCPUName; 15 | + (NSString *) getHardwareModel; 16 | + (NSString *) getKernelVersion; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64_x86_64-simulator/PPRiskMagnes.framework/Headers/PPRiskMagnes.h: -------------------------------------------------------------------------------- 1 | // 2 | // PPRiskMagnes.h 3 | // PPRiskMagnes 4 | // 5 | // Created by James Zhou on 7/31/17. 6 | // Copyright © 2017 PayPal Risk. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MagnesCryptoUtil.h" 11 | #import "MagnesSystemConfigUtils.h" 12 | //! Project version number for PPRiskMagnes. 13 | FOUNDATION_EXPORT double PPRiskMagnesVersionNumber; 14 | 15 | //! Project version string for PPRiskMagnes. 16 | FOUNDATION_EXPORT const unsigned char PPRiskMagnesVersionString[]; 17 | 18 | // In this header, you should import all the public headers of your framework using statements like #import 19 | -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64_x86_64-simulator/PPRiskMagnes.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64_x86_64-simulator/PPRiskMagnes.framework/Info.plist -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64_x86_64-simulator/PPRiskMagnes.framework/Modules/PPRiskMagnes.swiftmodule/arm64-apple-ios-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64_x86_64-simulator/PPRiskMagnes.framework/Modules/PPRiskMagnes.swiftmodule/arm64-apple-ios-simulator.swiftdoc -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64_x86_64-simulator/PPRiskMagnes.framework/Modules/PPRiskMagnes.swiftmodule/x86_64-apple-ios-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64_x86_64-simulator/PPRiskMagnes.framework/Modules/PPRiskMagnes.swiftmodule/x86_64-apple-ios-simulator.swiftdoc -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64_x86_64-simulator/PPRiskMagnes.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module PPRiskMagnes { 2 | umbrella header "PPRiskMagnes.h" 3 | export * 4 | 5 | module * { export * } 6 | } 7 | 8 | module PPRiskMagnes.Swift { 9 | header "PPRiskMagnes-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64_x86_64-simulator/PPRiskMagnes.framework/PPRiskMagnes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64_x86_64-simulator/PPRiskMagnes.framework/PPRiskMagnes -------------------------------------------------------------------------------- /Frameworks/XCFrameworks/PPRiskMagnes.xcframework/ios-arm64_x86_64-simulator/PPRiskMagnes.framework/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryUserDefaults 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | CA92.1 13 | 14 | 15 | 16 | NSPrivacyCollectedDataTypes 17 | 18 | 19 | NSPrivacyCollectedDataType 20 | NSPrivacyCollectedDataTypePerformanceData 21 | NSPrivacyCollectedDataTypeLinked 22 | 23 | NSPrivacyCollectedDataTypeTracking 24 | 25 | NSPrivacyCollectedDataTypePurposes 26 | 27 | NSPrivacyCollectedDataTypePurposeAppFunctionality 28 | 29 | 30 | 31 | NSPrivacyCollectedDataType 32 | NSPrivacyCollectedDataTypePreciseLocation 33 | NSPrivacyCollectedDataTypeLinked 34 | 35 | NSPrivacyCollectedDataTypeTracking 36 | 37 | NSPrivacyCollectedDataTypePurposes 38 | 39 | NSPrivacyCollectedDataTypePurposeAppFunctionality 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "PayPal", 8 | platforms: [.iOS(.v14)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, and make them visible to other packages. 11 | .library( 12 | name: "CorePayments", 13 | targets: ["CorePayments"] 14 | ), 15 | .library( 16 | name: "PaymentButtons", 17 | targets: ["PaymentButtons"] 18 | ), 19 | .library( 20 | name: "PayPalWebPayments", 21 | targets: ["PayPalWebPayments"] 22 | ), 23 | .library( 24 | name: "CardPayments", 25 | targets: ["CardPayments"] 26 | ), 27 | .library( 28 | name: "FraudProtection", 29 | targets: ["FraudProtection", "PPRiskMagnes"] 30 | ) 31 | ], 32 | targets: [ 33 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 34 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 35 | .target( 36 | name: "CorePayments", 37 | dependencies: [], 38 | resources: [.copy("PrivacyInfo.xcprivacy")] 39 | ), 40 | .target( 41 | name: "CardPayments", 42 | dependencies: ["CorePayments"], 43 | resources: [.copy("PrivacyInfo.xcprivacy")] 44 | ), 45 | .target( 46 | name: "PaymentButtons", 47 | dependencies: ["CorePayments"], 48 | resources: [.copy("PrivacyInfo.xcprivacy")] 49 | ), 50 | .target( 51 | name: "PayPalWebPayments", 52 | dependencies: ["CorePayments"], 53 | resources: [.copy("PrivacyInfo.xcprivacy")] 54 | ), 55 | .target( 56 | name: "FraudProtection", 57 | dependencies: ["CorePayments", "PPRiskMagnes"], 58 | resources: [.copy("PrivacyInfo.xcprivacy")] 59 | ), 60 | .binaryTarget( 61 | name: "PPRiskMagnes", 62 | path: "Frameworks/XCFrameworks/PPRiskMagnes.xcframework" 63 | ) 64 | ] 65 | ) 66 | -------------------------------------------------------------------------------- /PayPal.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "PayPal" 3 | s.version = "2.0.0" 4 | s.summary = "The PayPal iOS SDK: Helps you accept card, PayPal, and alternative payment methods in your iOS app." 5 | s.homepage = "https://developer.paypal.com/home" 6 | s.license = "MIT" 7 | s.author = { "PayPal" => "sdks@paypal.com" } 8 | s.source = { :git => "https://github.com/paypal/paypal-ios.git", :tag => s.version.to_s } 9 | s.swift_version = "5.9" 10 | 11 | s.platform = :ios, "14.0" 12 | s.compiler_flags = "-Wall -Werror -Wextra" 13 | 14 | s.subspec "CardPayments" do |s| 15 | s.source_files = "Sources/CardPayments/**/*.swift" 16 | s.dependency "PayPal/CorePayments" 17 | s.resource_bundle = { "CardPayments_PrivacyInfo" => "Sources/CardPayments/PrivacyInfo.xcprivacy" } 18 | end 19 | 20 | s.subspec "PaymentButtons" do |s| 21 | s.source_files = "Sources/PaymentButtons/*.swift" 22 | s.dependency "PayPal/CorePayments" 23 | s.resource_bundle = { 24 | 'PayPalSDK' => ['Sources/PaymentButtons/*.xcassets'], 25 | "PaymentButtons_PrivacyInfo" => "Sources/PaymentButtons/PrivacyInfo.xcprivacy" 26 | } 27 | end 28 | 29 | s.subspec "PayPalWebPayments" do |s| 30 | s.source_files = "Sources/PayPalWebPayments/*.swift" 31 | s.dependency "PayPal/CorePayments" 32 | s.resource_bundle = { "PayPalWebPayments_PrivacyInfo" => "Sources/PayPalWebPayments/PrivacyInfo.xcprivacy" } 33 | end 34 | 35 | s.subspec "FraudProtection" do |s| 36 | s.source_files = "Sources/FraudProtection/*.swift" 37 | s.dependency "PayPal/CorePayments" 38 | s.vendored_frameworks = "Frameworks/XCFrameworks/PPRiskMagnes.xcframework" 39 | s.resource_bundle = { "FraudProtection_PrivacyInfo" => "Sources/FraudProtection/PrivacyInfo.xcprivacy" } 40 | end 41 | 42 | s.subspec "CorePayments" do |s| 43 | s.source_files = "Sources/CorePayments/**/*.swift" 44 | s.resource_bundle = { "CorePayments_PrivacyInfo" => "Sources/CorePayments/PrivacyInfo.xcprivacy" } 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /PayPal.xcodeproj/PayPalTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /PayPal.xcodeproj/PayPal_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /PayPal.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PayPal.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PayPal.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /PayPal.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "paypalcheckout-ios", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/paypal/paypalcheckout-ios", 7 | "state" : { 8 | "revision" : "773568fbbffd54f900d6d78be7793ae1871d3b35", 9 | "version" : "1.0.0" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /PayPal.xcodeproj/xcshareddata/xcschemes/CardPaymentsTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /PayPal.xcodeproj/xcshareddata/xcschemes/CorePaymentsTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /PayPal.xcodeproj/xcshareddata/xcschemes/FraudProtectionTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /PayPal.xcodeproj/xcshareddata/xcschemes/PayPalNativePaymentsTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /PayPal.xcodeproj/xcshareddata/xcschemes/PayPalWebPaymentsTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /PayPal.xcodeproj/xcshareddata/xcschemes/PaymentButtonsTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /PayPal.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PayPal.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PayPal.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PayPal iOS SDK 2 | 3 | Welcome to PayPal's iOS SDK. This library will help you accept card, PayPal, Venmo, and alternative payment methods in your iOS app. 4 | 5 | ## FAQ 6 | ### Contribution 7 | If you have suggestions for features that you would like to see in future iterations of the SDK, please feel free to open an issue, PR, or discussion with suggestions. This product is fully open source. We welcome any and all feedback. 8 | 9 | ## Support 10 | 11 | The PayPal iOS SDK supports a minimum deployment target of iOS 14+ and requires Xcode 15.0+ and macOS Ventura 13. See our [Client Deprecation policy](https://developer.paypal.com/braintree/docs/guides/client-sdk/deprecation-policy/ios/v5) to plan for updates. 12 | 13 | ### Package Managers 14 | This SDK supports: 15 | 16 | * CocoaPods 17 | * Swift Package Manager 18 | 19 | ### Languages 20 | 21 | This SDK supports Swift 5.9+. This SDK is written in Swift. 22 | 23 | ### UI Frameworks 24 | This SDK supports: 25 | 26 | * UIKit 27 | * SwiftUI 28 | 29 | ## Client ID 30 | 31 | The PayPal SDK uses a client ID for authentication. This can be found in your [PayPal Developer Dashboard](https://developer.paypal.com/api/rest/#link-getstarted). 32 | 33 | ## Documentation 34 | 35 | Documentation for the PayPal iOS SDK can be found [here](https://developer.paypal.com/docs/checkout/advanced/ios/). 36 | 37 | ## Demo 38 | 39 | 1. Open `PayPal.xcworkspace` in Xcode 40 | 1. Resolve the Swift Package Manager packages if needed: `File` > `Packages` > `Resolve Package Versions` or by running `swift package resolve` in Terminal 41 | 1. Select the `Demo` scheme, and then run 42 | 43 | Xcode 14.3+ is required to run the demo app. 44 | 45 | ## Testing 46 | 47 | This project uses the `XCTest` framework provided by Xcode. Every code path should be unit tested. Unit tests should make up most of the test coverage, with integration, and then UI tests following. 48 | 49 | ### CI 50 | 51 | GitHub Actions CI will run all tests and build commands per package manager on each PR. 52 | 53 | ### Local Testing 54 | 55 | Our Xcode project uses [SwiftLint](https://github.com/realm/SwiftLint#installation). 56 | 57 | ## Release Process 58 | 59 | This SDK follows [Semantic Versioning](https://semver.org/). The release process will be automated via GitHub Actions. 60 | 61 | ## Analytics 62 | 63 | Client analytics will be collected via Lighthouse/FPTI. 64 | -------------------------------------------------------------------------------- /Resources/Fonts/PayPalOpen-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Resources/Fonts/PayPalOpen-Regular.otf -------------------------------------------------------------------------------- /SampleApps/SPMTest/SPMTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SampleApps/SPMTest/SPMTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SampleApps/SPMTest/SPMTest/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SampleApps/SPMTest/SPMTest/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /SampleApps/SPMTest/SPMTest/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SampleApps/SPMTest/SPMTest/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import CardPayments 3 | import PayPalWebPayments 4 | import FraudProtection 5 | import PaymentButtons 6 | import CorePayments 7 | 8 | struct ContentView: View { 9 | var body: some View { 10 | Text("Hello, world!") 11 | .padding() 12 | } 13 | } 14 | 15 | struct ContentView_Previews: PreviewProvider { 16 | static var previews: some View { 17 | ContentView() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /SampleApps/SPMTest/SPMTest/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SampleApps/SPMTest/SPMTest/SPMTestApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct SPMTestApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if canImport(CorePayments) 3 | import CorePayments 4 | #endif 5 | 6 | /// This class coordinates networking logic for communicating with the v2/checkout/orders API. 7 | /// 8 | /// Details on this PayPal API can be found in PPaaS under Merchant > Checkout > Orders > v2. 9 | class CheckoutOrdersAPI { 10 | 11 | // MARK: - Private Properties 12 | 13 | private let coreConfig: CoreConfig 14 | private let networkingClient: NetworkingClient 15 | 16 | // MARK: - Initializer 17 | 18 | init(coreConfig: CoreConfig) { 19 | self.coreConfig = coreConfig 20 | self.networkingClient = NetworkingClient(coreConfig: coreConfig) 21 | } 22 | 23 | /// Exposed for injecting MockNetworkingClient in tests 24 | init(coreConfig: CoreConfig, networkingClient: NetworkingClient) { 25 | self.coreConfig = coreConfig 26 | self.networkingClient = networkingClient 27 | } 28 | 29 | // MARK: - Internal Methods 30 | 31 | func confirmPaymentSource(cardRequest: CardRequest) async throws -> ConfirmPaymentSourceResponse { 32 | let confirmData = ConfirmPaymentSourceRequest(cardRequest: cardRequest) 33 | 34 | let restRequest = RESTRequest( 35 | path: "/v2/checkout/orders/\(cardRequest.orderID)/confirm-payment-source", 36 | method: .post, 37 | queryParameters: nil, 38 | postParameters: confirmData 39 | ) 40 | 41 | let httpResponse = try await networkingClient.fetch(request: restRequest) 42 | return try HTTPResponseParser().parseREST(httpResponse, as: ConfirmPaymentSourceResponse.self) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/CardPayments/APIRequests/ConfirmPaymentSourceResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if canImport(CorePayments) 3 | import CorePayments 4 | #endif 5 | 6 | struct ConfirmPaymentSourceResponse: Decodable { 7 | 8 | let id, status: String 9 | let paymentSource: PaymentSource? 10 | let links: [Link]? 11 | } 12 | 13 | struct Link: Decodable { 14 | 15 | let href: String? 16 | let rel, method: String? 17 | } 18 | -------------------------------------------------------------------------------- /Sources/CardPayments/APIRequests/UpdateSetupTokenResponse.swift: -------------------------------------------------------------------------------- 1 | struct UpdateSetupTokenResponse: Codable { 2 | 3 | let updateVaultSetupToken: TokenDetails 4 | } 5 | 6 | struct TokenDetails: Codable { 7 | 8 | struct Link: Codable { 9 | 10 | let rel: String 11 | let href: String 12 | } 13 | 14 | let id: String 15 | let status: String 16 | let links: [Link] 17 | } 18 | -------------------------------------------------------------------------------- /Sources/CardPayments/APIRequests/UpdateVaultVariables.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct UpdateVaultVariables: Encodable { 4 | 5 | // MARK: - Coding Keys 6 | 7 | private enum TopLevelKeys: String, CodingKey { 8 | case clientID 9 | case vaultSetupToken 10 | case paymentSource 11 | } 12 | 13 | private enum PaymentSourceKeys: String, CodingKey { 14 | case card 15 | } 16 | 17 | private enum CardKeys: String, CodingKey { 18 | case number 19 | case securityCode 20 | case expiry 21 | case name 22 | } 23 | 24 | // MARK: - Internal Properties 25 | // Exposed for testing 26 | 27 | let vaultRequest: CardVaultRequest 28 | let clientID: String 29 | 30 | // MARK: - Initializer 31 | 32 | init(cardVaultRequest: CardVaultRequest, clientID: String) { 33 | self.vaultRequest = cardVaultRequest 34 | self.clientID = clientID 35 | } 36 | 37 | // MARK: - Custom Encoder 38 | 39 | func encode(to encoder: Encoder) throws { 40 | var topLevel = encoder.container(keyedBy: TopLevelKeys.self) 41 | try topLevel.encode(clientID, forKey: .clientID) 42 | try topLevel.encode(vaultRequest.setupTokenID, forKey: .vaultSetupToken) 43 | 44 | var paymentSource = topLevel.nestedContainer(keyedBy: PaymentSourceKeys.self, forKey: .paymentSource) 45 | 46 | var card = paymentSource.nestedContainer(keyedBy: CardKeys.self, forKey: .card) 47 | try card.encode(vaultRequest.card.number, forKey: .number) 48 | try card.encode(vaultRequest.card.securityCode, forKey: .securityCode) 49 | try card.encode(vaultRequest.card.expiry, forKey: .expiry) 50 | try card.encodeIfPresent(vaultRequest.card.cardholderName, forKey: .name) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/CardPayments/APIRequests/VaultPaymentTokensAPI.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if canImport(CorePayments) 3 | import CorePayments 4 | #endif 5 | 6 | /// This class coordinates networking logic for communicating with the /graphql?UpdateVaultSetupToken API. 7 | class VaultPaymentTokensAPI { 8 | 9 | // MARK: - Private Propertires 10 | 11 | private let coreConfig: CoreConfig 12 | private let networkingClient: NetworkingClient 13 | 14 | // MARK: - Initializer 15 | 16 | init(coreConfig: CoreConfig) { 17 | self.coreConfig = coreConfig 18 | self.networkingClient = NetworkingClient(coreConfig: coreConfig) 19 | } 20 | 21 | /// Exposed for injecting MockNetworkingClient in tests 22 | init(coreConfig: CoreConfig, networkingClient: NetworkingClient) { 23 | self.coreConfig = coreConfig 24 | self.networkingClient = networkingClient 25 | } 26 | 27 | // MARK: - Internal Methods 28 | 29 | func updateSetupToken(cardVaultRequest: CardVaultRequest) async throws -> UpdateSetupTokenResponse { 30 | 31 | let queryString = """ 32 | mutation UpdateVaultSetupToken( 33 | $clientID: String!, 34 | $vaultSetupToken: String!, 35 | $paymentSource: PaymentSource 36 | ) { 37 | updateVaultSetupToken( 38 | clientId: $clientID 39 | vaultSetupToken: $vaultSetupToken 40 | paymentSource: $paymentSource 41 | ) { 42 | id, 43 | status, 44 | links { 45 | rel, 46 | href 47 | } 48 | } 49 | } 50 | """ 51 | 52 | let variables = UpdateVaultVariables(cardVaultRequest: cardVaultRequest, clientID: coreConfig.clientID) 53 | 54 | let graphQLRequest = GraphQLRequest( 55 | query: queryString, 56 | variables: variables, 57 | queryNameForURL: "UpdateVaultSetupToken" 58 | ) 59 | 60 | let httpResponse = try await networkingClient.fetch(request: graphQLRequest) 61 | 62 | return try HTTPResponseParser().parseGraphQL(httpResponse, as: UpdateSetupTokenResponse.self) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/CardPayments/Models/Address.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The postal address. 4 | public struct Address: Codable, Equatable { 5 | 6 | enum CodingKeys: String, CodingKey { 7 | case addressLine1 8 | case addressLine2 9 | case locality = "admin_area_2" 10 | case region = "admin_area_1" 11 | case postalCode 12 | case countryCode 13 | } 14 | 15 | /// Optional. The first line of the address. 16 | public var addressLine1: String? 17 | 18 | /// Optional. The second line of the address. 19 | public var addressLine2: String? 20 | 21 | /// Optional. A city, town, or village. Smaller than `region`. 22 | public var locality: String? 23 | 24 | /// Optional. The highest level sub-division in a country, which is usually a province or state. Format for postal delivery, for example, `CA` and not `California`. Value, by country, is: 25 | /// - UK: a county. 26 | /// - US: a state. 27 | /// - Canada: a province. 28 | /// - Japan: a prefecture. 29 | /// - Switzerland: a kanton. 30 | public var region: String? 31 | 32 | /// Optional. The postal code, which is the zip code or equivalent. Typically required for countries with a postal code or an equivalent. 33 | /// See [postal code](https://en.wikipedia.org/wiki/Postal_code). 34 | public var postalCode: String? 35 | 36 | /// The [two-character ISO 3166-1 code](/docs/integration/direct/rest/country-codes/) that identifies the country or region. 37 | /// Note: 38 | /// - The country code for Great Britain is GB and not UK as used in the top-level domain names for that country. Use the `C2` country code for China worldwide for comparable uncontrolled price (CUP) method, bank card, and cross-border transactions. 39 | public var countryCode: String 40 | 41 | public init( 42 | addressLine1: String? = nil, 43 | addressLine2: String? = nil, 44 | locality: String? = nil, 45 | region: String? = nil, 46 | postalCode: String? = nil, 47 | countryCode: String 48 | ) { 49 | self.addressLine1 = addressLine1 50 | self.addressLine2 = addressLine2 51 | self.locality = locality 52 | self.region = region 53 | self.postalCode = postalCode 54 | self.countryCode = countryCode 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/CardPayments/Models/Card.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Represents raw credit or debit card data provided by the customer. 4 | public struct Card: Encodable { 5 | 6 | /// The primary account number (PAN) for the payment card. 7 | public var number: String 8 | 9 | /// The 2-digit card expiration month in `MM` format 10 | public var expirationMonth: String 11 | 12 | /// The 4-digit card expiration year in `YYYY` format 13 | public var expirationYear: String 14 | 15 | /// The three- or four-digit security code of the card. Also known as the CVV, CVC, CVN, CVE, or CID. 16 | public var securityCode: String 17 | 18 | /// Optional. The card holder's name as it appears on the card. 19 | public var cardholderName: String? 20 | 21 | /// Optional. The billing address 22 | public var billingAddress: Address? 23 | 24 | /// This is exposed for convenience in copying card contents to UpdateSetupTokenQuery 25 | /// The expiration year and month, in ISO-8601 `YYYY-MM` date format. 26 | @_documentation(visibility: private) 27 | public var expiry: String { 28 | "\(expirationYear)-\(expirationMonth)" 29 | } 30 | 31 | public init( 32 | number: String, 33 | expirationMonth: String, 34 | expirationYear: String, 35 | securityCode: String, 36 | cardholderName: String? = nil, 37 | billingAddress: Address? = nil 38 | ) { 39 | self.number = number 40 | self.expirationMonth = expirationMonth 41 | self.expirationYear = expirationYear 42 | self.securityCode = securityCode 43 | self.cardholderName = cardholderName 44 | self.billingAddress = billingAddress 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/CardPayments/Models/CardRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct CardRequest { 4 | 5 | /// The order to approve 6 | public let orderID: String 7 | 8 | /// The card to be charged for this order 9 | public let card: Card 10 | 11 | /// 3DS authentication launch option 12 | public let sca: SCA 13 | 14 | /// Creates an instance of a card request 15 | /// - Parameters: 16 | /// - orderID: The order to be approved 17 | /// - card: The card to be charged for this order 18 | /// - sca: Specificy to always launch 3DS or only when required. Defaults to `scaWhenRequired`. 19 | public init(orderID: String, card: Card, sca: SCA = .scaWhenRequired) { 20 | self.orderID = orderID 21 | self.card = card 22 | self.sca = sca 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/CardPayments/Models/CardResult.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if canImport(CorePayments) 3 | import CorePayments 4 | #endif 5 | 6 | /// The result of a card payment flow. 7 | public struct CardResult { 8 | 9 | /// The order ID associated with the transaction 10 | public let orderID: String 11 | 12 | /// status of the order 13 | public let status: String? 14 | 15 | /// 3DS verification was attempted. Use v2/checkout/orders/{orderID} in your server to get verification results. 16 | public let didAttemptThreeDSecureAuthentication: Bool 17 | } 18 | -------------------------------------------------------------------------------- /Sources/CardPayments/Models/CardVaultRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A vault request to attach payment method to setup token 4 | public struct CardVaultRequest { 5 | 6 | /// The card for payment source to attach to the setup token 7 | public let card: Card 8 | 9 | /// ID for the setup token to update 10 | public let setupTokenID: String 11 | 12 | /// Creates an instance of a card vault request 13 | /// - Parameters: 14 | /// - card: The card for payment source to attach to the setup token 15 | /// - setupTokenID: An ID for the setup token to update 16 | public init(card: Card, setupTokenID: String) { 17 | self.card = card 18 | self.setupTokenID = setupTokenID 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/CardPayments/Models/CardVaultResult.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if canImport(CorePayments) 3 | import CorePayments 4 | #endif 5 | 6 | /// The result of a vault without purchase flow. 7 | public struct CardVaultResult { 8 | 9 | /// setupTokenID of token that was updated 10 | public let setupTokenID: String 11 | 12 | /// setup token status 13 | public let status: String? 14 | 15 | /// 3DS verification was attempted. Use v3/setup-tokens/{id} in your server to get verification results. 16 | public let didAttemptThreeDSecureAuthentication: Bool 17 | } 18 | -------------------------------------------------------------------------------- /Sources/CardPayments/Models/PaymentSource.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if canImport(CorePayments) 3 | import CorePayments 4 | #endif 5 | 6 | public struct PaymentSource: Decodable { 7 | 8 | /// The card used as payment 9 | public let card: PaymentSource.Card 10 | 11 | public struct Card: Decodable { 12 | 13 | /// The last four digits of the provided card 14 | public let lastFourDigits: String? 15 | 16 | /// The card network 17 | /// - Examples: "VISA", "MASTERCARD" 18 | public let brand: String? 19 | 20 | /// The type of the provided card. 21 | /// - Examples: "DEBIT", "CREDIT" 22 | public let type: String? 23 | 24 | /// The result of the 3DS challenge 25 | public let authenticationResult: AuthenticationResult? 26 | } 27 | } 28 | 29 | public struct AuthenticationResult: Decodable { 30 | 31 | public let liabilityShift: String? 32 | public let threeDSecure: ThreeDSecure? 33 | } 34 | 35 | public struct ThreeDSecure: Decodable { 36 | 37 | public let enrollmentStatus, authenticationStatus: String? 38 | } 39 | -------------------------------------------------------------------------------- /Sources/CardPayments/Models/SCA.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /// Strong Customer Authentication launching options 4 | public enum SCA: String { 5 | 6 | /// Launch SCA challenge for every transaction 7 | case scaAlways = "SCA_ALWAYS" 8 | 9 | /// Launch SCA challenge only when applicable 10 | case scaWhenRequired = "SCA_WHEN_REQUIRED" 11 | } 12 | -------------------------------------------------------------------------------- /Sources/CardPayments/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyCollectedDataTypes 6 | 7 | 8 | NSPrivacyCollectedDataType 9 | NSPrivacyCollectedDataTypePaymentInfo 10 | NSPrivacyCollectedDataTypeLinked 11 | 12 | NSPrivacyCollectedDataTypeTracking 13 | 14 | NSPrivacyCollectedDataTypePurposes 15 | 16 | NSPrivacyCollectedDataTypePurposeAppFunctionality 17 | 18 | 19 | 20 | NSPrivacyCollectedDataType 21 | NSPrivacyCollectedDataTypePhysicalAddress 22 | NSPrivacyCollectedDataTypeLinked 23 | 24 | NSPrivacyCollectedDataTypeTracking 25 | 26 | NSPrivacyCollectedDataTypePurposes 27 | 28 | NSPrivacyCollectedDataTypePurposeAppFunctionality 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Sources/CorePayments/CoreConfig.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The configuration object containing information required by every payment method. 4 | /// It is used to initialize all Client objects. 5 | public struct CoreConfig { 6 | 7 | public let environment: Environment 8 | public let clientID: String 9 | 10 | public init(clientID: String, environment: Environment) { 11 | self.environment = environment 12 | self.clientID = clientID 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/CorePayments/CorePaymentsError.swift: -------------------------------------------------------------------------------- 1 | public enum CorePaymentsError { 2 | 3 | static let domain = "CorePaymentsErrorDomain" 4 | 5 | enum Code: Int { 6 | /// 0. An unknown error occurred. 7 | case unknown 8 | 9 | /// 1. An error occured constructing the PayPal API URL 10 | case urlEncodingFailed 11 | } 12 | 13 | public static let urlEncodingFailed = CoreSDKError( 14 | code: Code.urlEncodingFailed.rawValue, 15 | domain: domain, 16 | errorDescription: "An error occured constructing the PayPal API URL. Contact developer.paypal.com/support." 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /Sources/CorePayments/CoreSDKError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Error structure that SDK errors conform to 4 | public struct CoreSDKError: Error, LocalizedError, Equatable { 5 | 6 | /// The error code. 7 | public let code: Int? 8 | 9 | /// A string containing the error domain. 10 | public let domain: String? 11 | 12 | /// A string containing the localized description of the error. 13 | public let errorDescription: String? 14 | 15 | public init(code: Int?, domain: String?, errorDescription: String?) { 16 | self.code = code 17 | self.domain = domain 18 | self.errorDescription = errorDescription 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/Enums/Environment.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // swiftlint:disable force_unwrapping 4 | public enum Environment { 5 | case sandbox 6 | case live 7 | 8 | var baseURL: URL { 9 | switch self { 10 | case .sandbox: 11 | return URL(string: "https://api-m.sandbox.paypal.com")! 12 | case .live: 13 | return URL(string: "https://api-m.paypal.com")! 14 | } 15 | } 16 | 17 | public var graphQLURL: URL { 18 | switch self { 19 | case .sandbox: 20 | return URL(string: "https://www.sandbox.paypal.com/graphql")! 21 | case .live: 22 | return URL(string: "https://www.paypal.com/graphql")! 23 | } 24 | } 25 | 26 | /// URL used to display the PayPal Vault w/o Purchase experience in web browser 27 | public var paypalVaultCheckoutURL: URL { 28 | switch self { 29 | case .sandbox: 30 | return URL(string: "https://sandbox.paypal.com/agreements/approve")! 31 | case .live: 32 | return URL(string: "https://paypal.com/agreements/approve")! 33 | } 34 | } 35 | 36 | public var toString: String { 37 | switch self { 38 | case .sandbox: 39 | return "sandbox" 40 | case .live: 41 | return "live" 42 | } 43 | } 44 | } 45 | // swiftlint:enable force_unwrapping 46 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/Enums/HTTPHeader.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum HTTPHeader: String { 4 | case accept = "Accept" 5 | case acceptLanguage = "Accept-Language" 6 | case appName = "x-app-name" 7 | case authorization = "Authorization" 8 | case contentType = "Content-Type" 9 | case origin = "Origin" 10 | } 11 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/Enums/HTTPMethod.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | /// Request method associated with a URL request 5 | /// For more information, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods 6 | /// Methods can be added here as they are needed 7 | public enum HTTPMethod: String { 8 | case get = "GET" 9 | case post = "POST" 10 | } 11 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/GraphQL/GraphQLErrorResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Used to parse error message details out of GraphQL HTTP response body 4 | struct GraphQLErrorResponse: Decodable { 5 | 6 | enum CodingKeys: String, CodingKey { 7 | case error = "error" 8 | case correlationID = "correlation_id" 9 | } 10 | 11 | let error: String 12 | let correlationID: String? 13 | } 14 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/GraphQL/GraphQLHTTPPostBody.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The GraphQL query and variable details encoded to be sent in the POST body of a HTTP request 4 | struct GraphQLHTTPPostBody: Encodable { 5 | 6 | private let query: String 7 | private let variables: Encodable 8 | 9 | enum CodingKeys: CodingKey { 10 | case query 11 | case variables 12 | } 13 | 14 | init(query: String, variables: Encodable) { 15 | self.query = query 16 | self.variables = variables 17 | } 18 | 19 | func encode(to encoder: Encoder) throws { 20 | var container = encoder.container(keyedBy: CodingKeys.self) 21 | try container.encode(self.query, forKey: .query) 22 | try container.encode(self.variables, forKey: .variables) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/GraphQL/GraphQLHTTPResponse.swift: -------------------------------------------------------------------------------- 1 | /// Used to decode the HTTP reponse body of GraphQL requests 2 | @_documentation(visibility: private) 3 | public struct GraphQLHTTPResponse: Decodable { 4 | 5 | public let data: T? 6 | } 7 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/GraphQL/GraphQLRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Values needed to initiate a GraphQL network request 4 | @_documentation(visibility: private) 5 | public struct GraphQLRequest { 6 | 7 | let query: String 8 | let variables: Encodable 9 | 10 | /// This is non-standard in the GraphQL language, but sometimes required by PayPal's GraphQL API. 11 | /// Some requests are sent to `https://www.api.paypal.com/graphql?` 12 | let queryNameForURL: String? 13 | 14 | public init(query: String, variables: Encodable, queryNameForURL: String? = nil) { 15 | self.query = query 16 | self.variables = variables 17 | self.queryNameForURL = queryNameForURL 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/HTTP.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// `HTTP` constructs `URLRequest`s and interfaces directly with `URLSession` to execute network requests. 4 | class HTTP { 5 | 6 | let coreConfig: CoreConfig 7 | private var urlSession: URLSessionProtocol 8 | 9 | init( 10 | urlSession: URLSessionProtocol = URLSession.shared, 11 | coreConfig: CoreConfig 12 | ) { 13 | self.urlSession = urlSession 14 | self.coreConfig = coreConfig 15 | } 16 | 17 | func performRequest(_ httpRequest: HTTPRequest) async throws -> HTTPResponse { 18 | var urlRequest = URLRequest(url: httpRequest.url) 19 | urlRequest.httpMethod = httpRequest.method.rawValue 20 | urlRequest.httpBody = httpRequest.body 21 | 22 | httpRequest.headers.forEach { key, value in 23 | urlRequest.addValue(value, forHTTPHeaderField: key.rawValue) 24 | } 25 | 26 | let (data, response): (Data, URLResponse) 27 | do { 28 | (data, response) = try await urlSession.performRequest(with: urlRequest) 29 | } catch _ as URLError { 30 | throw NetworkingError.urlSessionError 31 | } catch { 32 | throw NetworkingError.unknownError 33 | } 34 | 35 | guard let response = response as? HTTPURLResponse else { 36 | throw NetworkingError.invalidURLResponseError 37 | } 38 | 39 | return HTTPResponse(status: response.statusCode, body: data) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/HTTPRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Values needed to initiate a HTTP network request 4 | @_documentation(visibility: private) 5 | public struct HTTPRequest { 6 | 7 | let headers: [HTTPHeader: String] 8 | let method: HTTPMethod 9 | let url: URL 10 | let body: Data? 11 | 12 | public init( 13 | headers: [HTTPHeader: String], 14 | method: HTTPMethod, 15 | url: URL, 16 | body: Data? 17 | ) { 18 | self.headers = headers 19 | self.method = method 20 | self.url = url 21 | self.body = body 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/HTTPResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @_documentation(visibility: private) 4 | public struct HTTPResponse { 5 | 6 | let status: Int 7 | let body: Data? 8 | 9 | var isSuccessful: Bool { (200..<300).contains(status) } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/Models/ErrorResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ErrorResponse: Codable { 4 | 5 | let name: String 6 | let message: String? 7 | 8 | var readableDescription: String { 9 | if let message = message { 10 | return name + ": " + message 11 | } else { 12 | return name 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/RESTRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @_documentation(visibility: private) 4 | public struct RESTRequest { 5 | 6 | var path: String 7 | var method: HTTPMethod 8 | var queryParameters: [String: String]? 9 | var postParameters: Encodable? 10 | 11 | public init( 12 | path: String, 13 | method: HTTPMethod, 14 | queryParameters: [String: String]? = nil, 15 | postParameters: Encodable? = nil 16 | ) { 17 | self.path = path 18 | self.method = method 19 | self.queryParameters = queryParameters 20 | self.postParameters = postParameters 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/TrackingEventsAPI.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This class coordinates networking logic for communicating with the v1/tracking/events API. 4 | /// 5 | /// Details on this PayPal API can be found in PPaaS under Infrastructure > Experimentation > Tracking Events > v1. 6 | class TrackingEventsAPI { 7 | 8 | // MARK: - Internal Properties 9 | 10 | var coreConfig: CoreConfig // exposed for testing 11 | private var networkingClient: NetworkingClient 12 | 13 | // MARK: - Initializer 14 | 15 | init(coreConfig merchantConfig: CoreConfig) { 16 | // api-m.sandbox.paypal.com does not currently send FPTI events to BigQuery/Looker 17 | self.coreConfig = CoreConfig(clientID: merchantConfig.clientID, environment: .live) 18 | self.networkingClient = NetworkingClient(coreConfig: coreConfig) 19 | } 20 | 21 | /// Exposed for injecting MockNetworkingClient in tests 22 | init(coreConfig: CoreConfig, networkingClient: NetworkingClient) { 23 | self.coreConfig = coreConfig 24 | self.networkingClient = networkingClient 25 | } 26 | 27 | // MARK: - Internal Functions 28 | 29 | func sendEvent(with analyticsEventData: AnalyticsEventData) async throws -> HTTPResponse { 30 | let restRequest = RESTRequest( 31 | path: "v1/tracking/events", 32 | method: .post, 33 | queryParameters: nil, 34 | postParameters: analyticsEventData 35 | ) 36 | 37 | return try await networkingClient.fetch(request: restRequest) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/URLSession+URLSessionProtocol.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension URLSession: URLSessionProtocol { 4 | 5 | public func performRequest(with urlRequest: URLRequest) async throws -> (Data, URLResponse) { 6 | if #available(iOS 15, *) { 7 | return try await self.data(for: urlRequest) 8 | } else { 9 | return try await withCheckedThrowingContinuation { continuation in 10 | let task = self.dataTask(with: urlRequest) { data, response, error in 11 | guard let data = data, let response = response else { 12 | let error = error ?? URLError(.badServerResponse) 13 | return continuation.resume(throwing: error) 14 | } 15 | continuation.resume(returning: (data, response)) 16 | } 17 | task.resume() 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/CorePayments/Networking/URLSessionProtocol.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This is a protocol for function performing urlRequest in HTTP class and in GraphQLClient 4 | @_documentation(visibility: private) 5 | public protocol URLSessionProtocol { 6 | func performRequest(with urlRequest: URLRequest) async throws -> (Data, URLResponse) 7 | } 8 | -------------------------------------------------------------------------------- /Sources/CorePayments/PayPalCoreConstants.swift: -------------------------------------------------------------------------------- 1 | /// This class is exposed for internal PayPal use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time. 2 | @_documentation(visibility: private) 3 | public enum PayPalCoreConstants { 4 | 5 | // TODO: - Update release script to update this version # 6 | /// This property is exposed for internal PayPal use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time. 7 | public static let payPalSDKVersion: String = "2.0.0" 8 | 9 | public static let callbackURLScheme: String = "sdk.ios.paypal" 10 | } 11 | -------------------------------------------------------------------------------- /Sources/CorePayments/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyCollectedDataTypes 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/CorePayments/WebAuthenticationSession.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AuthenticationServices 3 | 4 | public class WebAuthenticationSession: NSObject { 5 | 6 | public func start( 7 | url: URL, 8 | context: ASWebAuthenticationPresentationContextProviding, 9 | sessionDidDisplay: @escaping (Bool) -> Void, 10 | sessionDidComplete: @escaping (URL?, Error?) -> Void 11 | ) { 12 | let authenticationSession = ASWebAuthenticationSession( 13 | url: url, 14 | callbackURLScheme: PayPalCoreConstants.callbackURLScheme, 15 | completionHandler: sessionDidComplete 16 | ) 17 | 18 | authenticationSession.presentationContextProvider = context 19 | 20 | DispatchQueue.main.async { 21 | sessionDidDisplay(authenticationSession.start()) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/FraudProtection/CoreConfig+MagnesSDK.swift: -------------------------------------------------------------------------------- 1 | import PPRiskMagnes 2 | #if canImport(CorePayments) 3 | import CorePayments 4 | #endif 5 | 6 | extension CoreConfig { 7 | 8 | var magnesEnvironment: MagnesSDK.Environment { 9 | switch environment { 10 | case .sandbox: 11 | return .SANDBOX 12 | case .live: 13 | return .LIVE 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/FraudProtection/DeviceInspector.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class DeviceInspector: DeviceInspectorProtocol { 4 | 5 | private let accountName = "com.paypal.ios-sdk.PayPalDataCollector.DeviceGUID" 6 | 7 | func payPalDeviceIdentifier() -> String { 8 | payPalDeviceIdentifier(newIdentifier: UUID()) 9 | } 10 | 11 | func payPalDeviceIdentifier(newIdentifier: UUID) -> String { 12 | if let deviceID = getDeviceIDFromKeychain() { 13 | return deviceID 14 | } 15 | 16 | let newDeviceID = newIdentifier.uuidString 17 | saveDeviceIDtoKeychain(newDeviceID) 18 | return newDeviceID 19 | } 20 | 21 | private func getDeviceIDFromKeychain() -> String? { 22 | let searchQuery: [String: Any] = [ 23 | kSecClass as String: kSecClassGenericPassword, 24 | kSecAttrAccount as String: accountName, 25 | kSecAttrService as String: "Service", 26 | kSecReturnData as String: true, 27 | kSecMatchLimit as String: kSecMatchLimitOne 28 | ] 29 | 30 | var item: CFTypeRef? 31 | let status = SecItemCopyMatching(searchQuery as CFDictionary, &item) 32 | if status == errSecSuccess, 33 | let existingItem = item as? [String: Any], 34 | let data = existingItem[kSecValueData as String] as? Data, 35 | let identifier = String(data: data, encoding: .utf8) { 36 | return identifier 37 | } 38 | return nil 39 | } 40 | 41 | private func saveDeviceIDtoKeychain(_ deviceID: String) { 42 | if let deviceIDData = deviceID.data(using: .utf8) { 43 | let saveQuery: [String: Any] = [ 44 | kSecClass as String: kSecClassGenericPassword, 45 | kSecAttrAccount as String: accountName, 46 | kSecAttrService as String: "Service", 47 | kSecValueData as String: deviceIDData, 48 | kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly 49 | ] 50 | _ = SecItemAdd(saveQuery as CFDictionary, nil) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/FraudProtection/DeviceInspectorProtocol.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol DeviceInspectorProtocol { 4 | func payPalDeviceIdentifier() -> String 5 | } 6 | -------------------------------------------------------------------------------- /Sources/FraudProtection/MagnesSDKProtocol.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PPRiskMagnes 3 | 4 | protocol MagnesSDKProtocol { 5 | func setUpWithParams(_ magnesParams: MagnesSetupParams) throws 6 | func collectDeviceData( 7 | withPayPalClientMetadataId cmid: String, 8 | withAdditionalData additionalData: [String: String] 9 | ) throws -> MagnesSDKResult 10 | } 11 | 12 | extension MagnesSDK: MagnesSDKProtocol { 13 | 14 | func setUpWithParams(_ params: MagnesSetupParams) throws { 15 | try setUp( 16 | setEnviroment: params.env, 17 | setOptionalAppGuid: params.appGuid, 18 | disableRemoteConfiguration: params.isRemoteConfigDisabled, 19 | disableBeacon: params.isBeaconDisabled, 20 | magnesSource: params.source 21 | ) 22 | } 23 | 24 | func collectDeviceData( 25 | withPayPalClientMetadataId cmid: String, 26 | withAdditionalData additionalData: [String: String] 27 | ) throws -> MagnesSDKResult { 28 | try collectAndSubmit(withPayPalClientMetadataId: cmid, withAdditionalData: additionalData) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/FraudProtection/MagnesSDKResult.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PPRiskMagnes 3 | 4 | protocol MagnesSDKResult { 5 | func getPayPalClientMetaDataId() -> String 6 | } 7 | 8 | extension MagnesResult: MagnesSDKResult {} 9 | -------------------------------------------------------------------------------- /Sources/FraudProtection/MagnesSetupParams.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PPRiskMagnes 3 | 4 | struct MagnesSetupParams { 5 | 6 | let env: MagnesSDK.Environment 7 | let appGuid: String 8 | let isRemoteConfigDisabled: Bool 9 | let isBeaconDisabled: Bool 10 | let source: MagnesSDK.MagnesSource 11 | } 12 | -------------------------------------------------------------------------------- /Sources/FraudProtection/PayPalDataCollector.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PPRiskMagnes 3 | #if canImport(CorePayments) 4 | import CorePayments 5 | #endif 6 | 7 | /// Enables you to collect data about a customer's device and correlate it with a session identifier on your server. 8 | public class PayPalDataCollector { 9 | 10 | // MARK: - Properties 11 | 12 | private let magnesSDK: MagnesSDKProtocol 13 | private let deviceInspector: DeviceInspectorProtocol 14 | private let magnesEnvironment: MagnesSDK.Environment 15 | 16 | // MARK: - Initializers 17 | 18 | /// Construct an instance to collect device data to send to your server. 19 | /// - Parameter environment: enviroment for the data collector 20 | public convenience init(config: CoreConfig) { 21 | self.init(config: config, magnesSDK: MagnesSDK.shared(), deviceInspector: DeviceInspector()) 22 | } 23 | 24 | /// internal constructor for testing 25 | init(config: CoreConfig, magnesSDK: MagnesSDKProtocol, deviceInspector: DeviceInspectorProtocol) { 26 | self.magnesEnvironment = config.magnesEnvironment 27 | self.magnesSDK = magnesSDK 28 | self.deviceInspector = deviceInspector 29 | } 30 | 31 | // MARK: - PayPalDataCollector 32 | 33 | /// Collects device data. 34 | /// - Parameter additionalData: additional key value pairs to correlate with device data 35 | /// - Returns: A JSON string containing a device data identifier that should be forwarded to your server 36 | public func collectDeviceData(additionalData: [String: String] = [:]) -> String { 37 | let deviceIdentifier = deviceInspector.payPalDeviceIdentifier() 38 | let params = MagnesSetupParams( 39 | env: magnesEnvironment, 40 | appGuid: deviceIdentifier, 41 | isRemoteConfigDisabled: false, 42 | isBeaconDisabled: false, 43 | source: .PAYPAL 44 | ) 45 | try? magnesSDK.setUpWithParams(params) 46 | 47 | let result = try? magnesSDK.collectDeviceData(withPayPalClientMetadataId: "", withAdditionalData: additionalData) 48 | let clientMetadataId = result?.getPayPalClientMetaDataId() ?? "" 49 | 50 | return "{\"correlation_id\":\"\(clientMetadataId)\"}" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/FraudProtection/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyCollectedDataTypes 6 | 7 | 8 | NSPrivacyCollectedDataType 9 | NSPrivacyCollectedDataTypeDeviceID 10 | NSPrivacyCollectedDataTypeLinked 11 | 12 | NSPrivacyCollectedDataTypeTracking 13 | 14 | NSPrivacyCollectedDataTypePurposes 15 | 16 | NSPrivacyCollectedDataTypePurposeAppFunctionality 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Sources/PayPalWebPayments/Environment+PayPalWebCheckout.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if canImport(CorePayments) 4 | import CorePayments 5 | #endif 6 | 7 | extension Environment { 8 | 9 | // swiftlint:disable force_unwrapping 10 | var payPalBaseURL: URL { 11 | switch self { 12 | case .sandbox: 13 | return URL(string: "https://www.sandbox.paypal.com")! 14 | case .live: 15 | return URL(string: "https://www.paypal.com")! 16 | } 17 | } 18 | // swiftlint:enable force_unwrapping 19 | } 20 | -------------------------------------------------------------------------------- /Sources/PayPalWebPayments/PayPalVaultRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A request to vault a PayPal payment method 4 | public struct PayPalVaultRequest { 5 | 6 | // NEXT_MAJOR_VERSION: - Remove URL property 7 | /// PayPal approval URL returned as the `href` from the setup token API call 8 | public let url: URL? = nil 9 | 10 | /// ID for the setup token associated with the vault 11 | /// Returned as top level `id` from the setup token API call 12 | public let setupTokenID: String 13 | 14 | /// Creates an instance of a PayPal vault request 15 | /// - Parameters: 16 | /// - url: PayPal approval URL returned as the `href` from the setup token API call 17 | /// - setupTokenID: An ID for the setup token associated with the vault 18 | @available(*, deprecated, message: "Use `init(setupTokenID:)` instead.") 19 | public init(url: URL, setupTokenID: String) { 20 | self.setupTokenID = setupTokenID 21 | } 22 | 23 | /// Creates an instance of a PayPal vault request 24 | /// - Parameter setupTokenID: An ID for the setup token associated with the vault 25 | public init(setupTokenID: String) { 26 | self.setupTokenID = setupTokenID 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/PayPalWebPayments/PayPalVaultResult.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if canImport(CorePayments) 3 | import CorePayments 4 | #endif 5 | 6 | /// The result of a vault without purchase flow. 7 | public struct PayPalVaultResult: Decodable, Equatable { 8 | 9 | public let tokenID: String 10 | public let approvalSessionID: String 11 | } 12 | -------------------------------------------------------------------------------- /Sources/PayPalWebPayments/PayPalWebCheckoutFundingSource.swift: -------------------------------------------------------------------------------- 1 | /// Enum class to specify the type of funding for an order. 2 | /// For more information go to: https://developer.paypal.com/docs/checkout/pay-later/us/ 3 | public enum PayPalWebCheckoutFundingSource: String { 4 | 5 | /// PayPal Credit will launch the web checkout flow and display PayPal Credit funding to eligible customers 6 | /// Eligible costumers receive a revolving line of credit that they can use to pay over time. 7 | case paypalCredit = "credit" 8 | 9 | // NEXT_MAJOR_VERSION: rename to `payLater` 10 | /// PayLater will launch the web checkout flow and display Pay Later offers to eligible customers, 11 | /// which include short-term, interest-free payments and other special financing options 12 | case paylater = "paylater" 13 | 14 | /// PayPal will launch the web checkout for a one-time PayPal Checkout flow 15 | case paypal = "paypal" 16 | } 17 | -------------------------------------------------------------------------------- /Sources/PayPalWebPayments/PayPalWebCheckoutRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Used to configure options for approving a PayPal web order 4 | public struct PayPalWebCheckoutRequest { 5 | 6 | /// The order ID associated with the request. 7 | public let orderID: String 8 | /// The funding for the order: credit, paylater or default 9 | public let fundingSource: PayPalWebCheckoutFundingSource 10 | 11 | /// Creates an instance of a PayPalRequest. 12 | /// - Parameter orderID: The ID of the order to be approved. 13 | /// - Parameter fundingSource: The funding source for and order. Default value is .paypal 14 | public init(orderID: String, fundingSource: PayPalWebCheckoutFundingSource = .paypal) { 15 | self.orderID = orderID 16 | self.fundingSource = fundingSource 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/PayPalWebPayments/PayPalWebCheckoutResult.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The result of a PayPal web payment flow. 4 | public struct PayPalWebCheckoutResult { 5 | 6 | /// The order ID associated with the transaction. 7 | public let orderID: String 8 | 9 | /// The Payer ID (or user id) associated with the transaction. 10 | public let payerID: String 11 | } 12 | -------------------------------------------------------------------------------- /Sources/PayPalWebPayments/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyCollectedDataTypes 6 | 7 | 8 | NSPrivacyCollectedDataType 9 | NSPrivacyCollectedDataTypePaymentInfo 10 | NSPrivacyCollectedDataTypeLinked 11 | 12 | NSPrivacyCollectedDataTypeTracking 13 | 14 | NSPrivacyCollectedDataTypePurposes 15 | 16 | NSPrivacyCollectedDataTypePurposeAppFunctionality 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/credit_color.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "paypal-credit-color.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "paypal-credit-color@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "paypal-credit-color@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/credit_color.imageset/paypal-credit-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/credit_color.imageset/paypal-credit-color.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/credit_color.imageset/paypal-credit-color@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/credit_color.imageset/paypal-credit-color@2x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/credit_color.imageset/paypal-credit-color@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/credit_color.imageset/paypal-credit-color@3x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/credit_monochrome.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "paypal-credit-monochrome.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "paypal-credit-monochrome@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "paypal-credit-monochrome@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/credit_monochrome.imageset/paypal-credit-monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/credit_monochrome.imageset/paypal-credit-monochrome.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/credit_monochrome.imageset/paypal-credit-monochrome@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/credit_monochrome.imageset/paypal-credit-monochrome@2x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/credit_monochrome.imageset/paypal-credit-monochrome@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/credit_monochrome.imageset/paypal-credit-monochrome@3x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_blue.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "paypal_blue@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "paypal_blue@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_blue.imageset/paypal_blue@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/paypal_blue.imageset/paypal_blue@2x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_blue.imageset/paypal_blue@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/paypal_blue.imageset/paypal_blue@3x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_color.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "paypal_color@2x-1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "paypal_color@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "paypal_color@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_color.imageset/paypal_color@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/paypal_color.imageset/paypal_color@2x-1.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_color.imageset/paypal_color@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/paypal_color.imageset/paypal_color@2x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_color.imageset/paypal_color@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/paypal_color.imageset/paypal_color@3x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monochrome.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "paypal_monochrome@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "paypal_monochrome@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monochrome.imageset/paypal_monochrome@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monochrome.imageset/paypal_monochrome@2x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monochrome.imageset/paypal_monochrome@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monochrome.imageset/paypal_monochrome@3x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_blue.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "paypal_blue_monogram@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "paypal_blue_monogram@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_blue.imageset/paypal_blue_monogram@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_blue.imageset/paypal_blue_monogram@2x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_blue.imageset/paypal_blue_monogram@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_blue.imageset/paypal_blue_monogram@3x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_color.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "paypal_color_monogram@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "paypal_color_monogram@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_color.imageset/paypal_color_monogram@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_color.imageset/paypal_color_monogram@2x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_color.imageset/paypal_color_monogram@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_color.imageset/paypal_color_monogram@3x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_monochrome.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "paypal_black_monogram@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "paypal_black_monogram@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_monochrome.imageset/paypal_black_monogram@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_monochrome.imageset/paypal_black_monogram@2x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_monochrome.imageset/paypal_black_monogram@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-ios/05582d305d28f97de7f700c4c33113031b43f6c8/Sources/PaymentButtons/Assets.xcassets/Logos/paypal_monogram_monochrome.imageset/paypal_black_monogram@3x.png -------------------------------------------------------------------------------- /Sources/PaymentButtons/Coordinator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Coordinator class for passing actions to our UIKit buttons in SwiftUI 4 | public class Coordinator { 5 | 6 | var action: () -> Void 7 | 8 | public init(action: @escaping () -> Void) { 9 | self.action = action 10 | } 11 | 12 | @objc func onAction(_ sender: Any) { 13 | action() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/PaymentButton+ImageAsset.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension PaymentButton { 4 | 5 | // MARK: - Internal Properties 6 | 7 | var image: UIImage? { 8 | UIImage(named: fileName, in: PaymentButton.bundle, compatibleWith: nil) 9 | } 10 | 11 | var imageAccessibilityLabel: String { 12 | // NEXT_MAJOR_VERSION: - To be replaced with translation strings. 13 | fileName.starts(with: "credit") ? "PayPal Credit" : "PayPal" 14 | } 15 | 16 | // MARK: - Private Properties 17 | 18 | /// Name of the sized `.imageset` assets within `Assets.xcassets` directory 19 | private var fileName: String { 20 | var imageAssetString = "" 21 | switch fundingSource { 22 | case .payPal: 23 | imageAssetString += "paypal_" 24 | 25 | if size == .mini { 26 | imageAssetString += "monogram_" 27 | } 28 | 29 | case .payLater: 30 | imageAssetString += "paypal_monogram_" 31 | 32 | case .credit: 33 | imageAssetString += "credit_" 34 | } 35 | 36 | switch color { 37 | case .gold, .white, .silver: 38 | imageAssetString += "color" 39 | 40 | case .black, .darkBlue: 41 | imageAssetString += "monochrome" 42 | 43 | case .blue: 44 | imageAssetString += "blue" 45 | } 46 | 47 | return imageAssetString 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/PaymentButtonEdges.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /// Edges for the smart payment button, these affect the corner radius. 4 | public enum PaymentButtonEdges: Equatable { 5 | 6 | /// Hard edges on button with 0 corner radius. 7 | case hardEdges 8 | 9 | /// Soft edges with a corner radius of 4 pts. 10 | case softEdges 11 | 12 | /// Pill shaped corner radius. 13 | case rounded 14 | 15 | /// Custom corner radius. 16 | case custom(CGFloat) 17 | 18 | func cornerRadius(for view: UIView) -> CGFloat { 19 | switch self { 20 | case .hardEdges: 21 | return 0.0 22 | 23 | case .softEdges: 24 | return 4.0 25 | 26 | case .rounded: 27 | return view.frame.size.height / 2 28 | 29 | case .custom(let cornerRadius): 30 | return min(cornerRadius, view.frame.size.height / 2) 31 | } 32 | } 33 | public var description: String { 34 | switch self { 35 | case .hardEdges: 36 | return "hardEdges" 37 | 38 | case .softEdges: 39 | return "softEdges" 40 | 41 | case .rounded: 42 | return "rounded" 43 | 44 | case .custom: 45 | return "custom" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/PaymentButtonFont.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | enum PaymentButtonFont { 4 | /// The primary font 5 | public static let paypalPrimaryFont = UIFont(name: "PayPalOpen-Regular", size: 14) ?? 6 | .systemFont(ofSize: UIFont.systemFontSize) 7 | } 8 | 9 | extension UIFont { 10 | 11 | private static func registerFont(withName name: String, fileExtension: String) { 12 | var errorRef: Unmanaged? 13 | let frameworkBundle = Bundle(for: PaymentButton.self) 14 | 15 | guard UIFont(name: name, size: 10.0) == nil else { 16 | return 17 | } 18 | 19 | guard 20 | let pathForResourceString = frameworkBundle.path(forResource: name, ofType: fileExtension), 21 | let fontData = NSData(contentsOfFile: pathForResourceString), 22 | let dataProvider = CGDataProvider(data: fontData), 23 | let fontRef = CGFont(dataProvider) else { return } 24 | print("Font file path:", pathForResourceString) 25 | 26 | if !CTFontManagerRegisterGraphicsFont(fontRef, &errorRef) { 27 | print("Error registering font") 28 | } 29 | } 30 | 31 | static func registerFont() { 32 | registerFont(withName: "PayPalOpen-Regular", fileExtension: "otf") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/PaymentButtonFundingSource.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The funding source to be used when checkout with PaymentButton 4 | public enum PaymentButtonFundingSource: String { 5 | case payPal = "PayPal" 6 | case payLater = "Pay Later" 7 | case credit = "Credit" 8 | } 9 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/PaymentButtonLabel.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The label displayed next to PaymentButton's logo. 4 | public enum PaymentButtonLabel: String { 5 | 6 | /// Add "Checkout" to the right of button's logo 7 | case checkout = "Checkout" 8 | 9 | /// Add "Buy Now" to the right of button's logo 10 | case buyNow = "Buy Now" 11 | 12 | /// Add "Pay with" to the left of button's logo 13 | case payWith = "Pay with" 14 | 15 | /// Add "Pay later" to the right of button's logo, only used for PayPalPayLaterButton 16 | case payLater = "Pay Later" 17 | 18 | enum Position { 19 | case prefix 20 | case suffix 21 | } 22 | 23 | var position: Position { 24 | switch self { 25 | case .checkout, .buyNow, .payLater: 26 | return .suffix 27 | 28 | case .payWith: 29 | return .prefix 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/PaymentButtonSize.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /// The size category which determines how the button is shown. 4 | public enum PaymentButtonSize: Int, CustomStringConvertible { 5 | 6 | /// Circle shaped button similar to a floating action button will show the monogram, if `.venmo` then will show `Venmo` logo. 7 | case mini 8 | 9 | /// Collapsed will only show the wordmark. 10 | case collapsed 11 | 12 | /// Expanded shows the wordmark along with the suffix. 13 | case expanded 14 | 15 | /// Full will show the wordmark along with the prefix and suffix. 16 | case full 17 | 18 | var font: UIFont { 19 | PaymentButtonFont.paypalPrimaryFont 20 | } 21 | 22 | var elementSpacing: CGFloat { 23 | switch self { 24 | case .mini, .collapsed: 25 | return 4.0 26 | 27 | case .expanded: 28 | return 4.5 29 | 30 | case .full: 31 | return 6.0 32 | } 33 | } 34 | 35 | var elementPadding: NSDirectionalEdgeInsets { 36 | switch self { 37 | case .mini: 38 | return NSDirectionalEdgeInsets( 39 | top: 14.0, 40 | leading: 14.0, 41 | bottom: 14.0, 42 | trailing: 14.0 43 | ) 44 | 45 | case .collapsed: 46 | return NSDirectionalEdgeInsets( 47 | top: 15.0, 48 | leading: 20.0, 49 | bottom: 15.0, 50 | trailing: 20.0 51 | ) 52 | 53 | case .expanded: 54 | return NSDirectionalEdgeInsets( 55 | top: 13.0, 56 | leading: 24.0, 57 | bottom: 13.0, 58 | trailing: 24.0 59 | ) 60 | 61 | case .full: 62 | return NSDirectionalEdgeInsets( 63 | top: 15.0, 64 | leading: 44.0, 65 | bottom: 15.0, 66 | trailing: 44.0 67 | ) 68 | } 69 | } 70 | 71 | public var description: String { 72 | switch self { 73 | case .mini: 74 | return "mini" 75 | 76 | case .collapsed: 77 | return "collapsed" 78 | 79 | case .expanded: 80 | return "expanded" 81 | 82 | case .full: 83 | return "full" 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Sources/PaymentButtons/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyCollectedDataTypes 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /UnitTests/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | # Reference: https://github.com/realm/SwiftLint 2 | # Required Swiftlint Version 3 | # swiftlint_version: 0.39.2 4 | 5 | disabled_rules: 6 | - todo 7 | - type_name # tests will have have the format _Tests 8 | - xctfail_message 9 | - implicitly_unwrapped_optional 10 | - force_unwrapping 11 | - force_cast 12 | - force_try 13 | - function_parameter_count 14 | -------------------------------------------------------------------------------- /UnitTests/CardPaymentsTests/MockCardVaultDelegate.swift: -------------------------------------------------------------------------------- 1 | @testable import CorePayments 2 | @testable import CardPayments 3 | 4 | class MockCardVaultDelegate: CardVaultDelegate { 5 | 6 | private var success: ((CardClient, CardVaultResult) -> Void)? 7 | private var failure: ((CardClient, CoreSDKError) -> Void)? 8 | 9 | required init( 10 | success: ((CardClient, CardVaultResult) -> Void)? = nil, 11 | error: ((CardClient, CoreSDKError) -> Void)? = nil 12 | ) { 13 | self.success = success 14 | self.failure = error 15 | } 16 | 17 | func card(_ cardClient: CardClient, didFinishWithVaultResult vaultResult: CardPayments.CardVaultResult) { 18 | success?(cardClient, vaultResult) 19 | } 20 | 21 | func card(_ cardClient: CardClient, didFinishWithVaultError vaultError: CorePayments.CoreSDKError) { 22 | failure?(cardClient, vaultError) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /UnitTests/CardPaymentsTests/MockGraphQLClient.swift: -------------------------------------------------------------------------------- 1 | @testable import CorePayments 2 | 3 | class MockGraphQLClient: GraphQLClient { 4 | 5 | var mockSuccessResponse: GraphQLQueryResponse? 6 | var mockErrorResponse: Error? 7 | 8 | override func callGraphQL( 9 | name: String, 10 | query: Q 11 | ) async throws -> GraphQLQueryResponse where T: Decodable, T: Encodable, Q: GraphQLQuery { 12 | if let response = mockSuccessResponse as? GraphQLQueryResponse { 13 | return response 14 | } else if let error = mockErrorResponse { 15 | throw error 16 | } else { 17 | fatalError("MockGraphQLClient - either mockSuccessResponse or mockErrorResponse must be set") 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /UnitTests/CardPaymentsTests/Mocks/FakeConfirmPaymentResponse.swift: -------------------------------------------------------------------------------- 1 | @testable import CardPayments 2 | 3 | enum FakeConfirmPaymentResponse { 4 | 5 | static let withValid3DSURL = ConfirmPaymentSourceResponse( 6 | id: "testOrderId", 7 | status: "PAYER_ACTION_REQUIRED", 8 | paymentSource: PaymentSource( 9 | card: PaymentSource.Card( 10 | lastFourDigits: "7321", 11 | brand: "VISA", 12 | type: "CREDIT", 13 | authenticationResult: nil 14 | ) 15 | ), 16 | links: [ 17 | Link( 18 | href: "https://fakeURL/helios?flow=3ds", 19 | rel: "payer-action", 20 | method: nil 21 | ) 22 | ] 23 | ) 24 | 25 | static let withInvalid3DSURL = ConfirmPaymentSourceResponse( 26 | id: "testOrderId", 27 | status: "PAYER_ACTION_REQUIRED", 28 | paymentSource: PaymentSource( 29 | card: PaymentSource.Card( 30 | lastFourDigits: "7321", 31 | brand: "VISA", 32 | type: "CREDIT", 33 | authenticationResult: nil 34 | ) 35 | ), 36 | links: [ 37 | Link( 38 | href: "https://sandbox.paypal.com", 39 | rel: "payer-action", 40 | method: nil 41 | ) 42 | ] 43 | ) 44 | 45 | static let without3DS = ConfirmPaymentSourceResponse( 46 | id: "testOrderId", 47 | status: "APPROVED", 48 | paymentSource: PaymentSource( 49 | card: PaymentSource.Card( 50 | lastFourDigits: "7321", 51 | brand: "VISA", 52 | type: "CREDIT", 53 | authenticationResult: nil 54 | ) 55 | ), 56 | links: nil 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /UnitTests/CardPaymentsTests/Mocks/FakeUpdateSetupTokenResponse.swift: -------------------------------------------------------------------------------- 1 | @testable import CardPayments 2 | 3 | enum FakeUpdateSetupTokenResponse { 4 | 5 | static let withValid3DSURL = UpdateSetupTokenResponse( 6 | updateVaultSetupToken: TokenDetails( 7 | id: "testSetupTokenId", 8 | status: "PAYER_ACTION_REQUIRED", 9 | links: [ 10 | TokenDetails.Link( 11 | rel: "approve", 12 | href: "https://www.sandbox.paypal.com/webapps/helios?action=authenticate&token=testSetupTokenId" 13 | ) 14 | ] 15 | ) 16 | ) 17 | 18 | static let withInvalid3DSURL = UpdateSetupTokenResponse( 19 | updateVaultSetupToken: TokenDetails( 20 | id: "testSetupTokenId", 21 | status: "PAYER_ACTION_REQUIRED", 22 | links: [ 23 | TokenDetails.Link( 24 | rel: "approve", 25 | href: "https://www.sandbox.paypal.com/" 26 | ) 27 | ] 28 | ) 29 | ) 30 | 31 | static let without3DS = UpdateSetupTokenResponse( 32 | updateVaultSetupToken: TokenDetails( 33 | id: "testSetupTokenId", 34 | status: "APPROVED", 35 | links: [ 36 | TokenDetails.Link( 37 | rel: "approve", 38 | href: "https://www.sandbox.paypal.com/" 39 | ) 40 | ] 41 | ) 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /UnitTests/CardPaymentsTests/Mocks/MockCheckoutOrdersAPI.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import CardPayments 3 | @testable import CorePayments 4 | 5 | class MockCheckoutOrdersAPI: CheckoutOrdersAPI { 6 | 7 | var stubConfirmResponse: ConfirmPaymentSourceResponse? 8 | var stubError: Error? 9 | 10 | var capturedCardRequest: CardRequest? 11 | 12 | override func confirmPaymentSource(cardRequest: CardRequest) async throws -> ConfirmPaymentSourceResponse { 13 | capturedCardRequest = cardRequest 14 | 15 | if let stubError { 16 | throw stubError 17 | } 18 | 19 | if let stubConfirmResponse { 20 | return stubConfirmResponse 21 | } 22 | 23 | throw CoreSDKError(code: 0, domain: "", errorDescription: "Stubbed responses not implemented for this mock.") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /UnitTests/CardPaymentsTests/Mocks/MockVaultPaymentTokensAPI.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import CardPayments 3 | @testable import CorePayments 4 | 5 | class MockVaultPaymentTokensAPI: VaultPaymentTokensAPI { 6 | 7 | var stubSetupTokenResponse: UpdateSetupTokenResponse? 8 | var stubError: Error? 9 | 10 | var capturedCardVaultRequest: CardVaultRequest? 11 | 12 | override func updateSetupToken(cardVaultRequest: CardVaultRequest) async throws -> UpdateSetupTokenResponse { 13 | capturedCardVaultRequest = cardVaultRequest 14 | 15 | if let stubError { 16 | throw stubError 17 | } 18 | 19 | if let stubSetupTokenResponse { 20 | return stubSetupTokenResponse 21 | } 22 | 23 | throw CoreSDKError(code: 0, domain: "", errorDescription: "Stubbed responses not implemented for this mock.") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /UnitTests/FraudProtectionTests/DeviceInspector_Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import FraudProtection 3 | 4 | class DeviceInspector_Tests: XCTestCase { 5 | 6 | func testPayPalDeviceIdentifier_shouldReturnANewIdentifierIfOneDoesNotExist() { 7 | clearPayPalDeviceIdentifier() 8 | 9 | let sut = DeviceInspector() 10 | let newIdentifier = UUID() 11 | let result = sut.payPalDeviceIdentifier(newIdentifier: newIdentifier) 12 | 13 | XCTAssertEqual(newIdentifier.uuidString, result) 14 | } 15 | 16 | func testPayPalDeviceIdentifier_shouldReturnAnExistingIdentifierIfOneDoesExist() throws { 17 | // Ref: https://developer.apple.com/forums/thread/60617 18 | #if targetEnvironment(simulator) 19 | throw XCTSkip("Keychain Access does not work in simulator") 20 | #else 21 | let sut = DeviceInspector() 22 | let newIdentifier = UUID() 23 | let result = sut.payPalDeviceIdentifier(newIdentifier: newIdentifier) 24 | 25 | XCTAssertNotEqual(newIdentifier.uuidString, result) 26 | #endif 27 | } 28 | 29 | func clearPayPalDeviceIdentifier() { 30 | let accountName = "com.paypal.ios-sdk.PayPalDataCollector.DeviceGUID" 31 | let query: [String: Any] = [ 32 | kSecClass as String: kSecClassGenericPassword, 33 | kSecAttrService as String: "Service", 34 | kSecAttrAccount as String: accountName 35 | ] 36 | _ = SecItemDelete(query as CFDictionary) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /UnitTests/FraudProtectionTests/MockDeviceInspector.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import FraudProtection 3 | 4 | class MockDeviceInspector: DeviceInspectorProtocol { 5 | 6 | var payPalDeviceIdentifierValue: String = "" 7 | 8 | func payPalDeviceIdentifier() -> String { 9 | return payPalDeviceIdentifierValue 10 | } 11 | 12 | func stubPayPalDeviceIdentifierWithValue(_ value: String) { 13 | payPalDeviceIdentifierValue = value 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /UnitTests/FraudProtectionTests/MockMagnesSDK.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PPRiskMagnes 3 | @testable import FraudProtection 4 | 5 | struct CollectDeviceDataArgs: Hashable { 6 | 7 | let payPalClientMetadataId: String 8 | let additionalData: [String: String] 9 | } 10 | 11 | class MockMagnesSDK: MagnesSDKProtocol { 12 | 13 | var collectDeviceDataStubs: [CollectDeviceDataArgs: MockMagnesSDKResult] = [:] 14 | 15 | var capturedSetupParams: MagnesSetupParams? 16 | 17 | func setUpWithParams(_ magnesParams: MagnesSetupParams) throws { 18 | capturedSetupParams = magnesParams 19 | } 20 | 21 | func collectDeviceData( 22 | withPayPalClientMetadataId cmid: String, 23 | withAdditionalData additionalData: [String: String] 24 | ) throws -> MagnesSDKResult { 25 | let args = CollectDeviceDataArgs(payPalClientMetadataId: cmid, additionalData: additionalData) 26 | return collectDeviceDataStubs[args] ?? MockMagnesSDKResult() 27 | } 28 | 29 | func stubCollectDeviceData(forArgs args: CollectDeviceDataArgs, withValue result: MockMagnesSDKResult) { 30 | collectDeviceDataStubs[args] = result 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /UnitTests/FraudProtectionTests/MockMagnesSDKResult.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import FraudProtection 3 | 4 | class MockMagnesSDKResult: MagnesSDKResult { 5 | 6 | var payPalClientMetaDataId: String 7 | 8 | init(payPalClientMetaDataId: String = "") { 9 | self.payPalClientMetaDataId = payPalClientMetaDataId 10 | } 11 | 12 | func getPayPalClientMetaDataId() -> String { 13 | return payPalClientMetaDataId 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /UnitTests/PayPalWebPaymentsTests/Environment+PayPalWebCheckout_Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CorePayments 3 | @testable import PayPalWebPayments 4 | 5 | class Environment_PayPalWebCheckout_Tests: XCTestCase { 6 | 7 | func testPayPalEnvironment_returnsCorrectBaseURL() { 8 | let sandbox = Environment.sandbox 9 | let live = Environment.live 10 | 11 | XCTAssertEqual(sandbox.payPalBaseURL.absoluteString, "https://www.sandbox.paypal.com") 12 | XCTAssertEqual(live.payPalBaseURL.absoluteString, "https://www.paypal.com") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /UnitTests/PaymentButtonsTests/Coordinator_Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PaymentButtons 3 | 4 | class Coordinator_Tests: XCTestCase { 5 | 6 | var actionString: String = "" 7 | 8 | func testCoordinator_init() { 9 | let action = { self.actionTest() } 10 | let coordinator = Coordinator(action: action) 11 | 12 | coordinator.onAction(UIViewController()) 13 | 14 | XCTAssertEqual(actionString, "Test action") 15 | } 16 | 17 | private func actionTest() { 18 | actionString = "Test action" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /UnitTests/PaymentButtonsTests/PayPalButton_Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PaymentButtons 3 | 4 | class PayPalButton_Tests: XCTestCase { 5 | 6 | // MARK: - PayPalButton for UIKit 7 | 8 | func testInit_whenPayPalButtonCreated_hasUIImageFromAssets() { 9 | let sut = PayPalButton() 10 | XCTAssertEqual(sut.imageView?.image, UIImage(named: "PayPalLogo")) 11 | } 12 | 13 | func testInit_whenPayPalButtonCreated_hasDefaultUIValuess() { 14 | let sut = PayPalButton() 15 | XCTAssertEqual(sut.edges, PaymentButtonEdges.softEdges) 16 | XCTAssertEqual(sut.size, PaymentButtonSize.collapsed) 17 | XCTAssertEqual(sut.color, PaymentButtonColor.gold) 18 | XCTAssertNil(sut.label) 19 | XCTAssertNil(sut.insets) 20 | } 21 | 22 | // MARK: - PayPalButton.Representable for SwiftUI 23 | 24 | func testMakeCoordinator_whenOnActionIsCalled_executesActionPassedInInitializer() { 25 | let expectation = expectation(description: "Action is called") 26 | let sut = PayPalButton.Representable { 27 | expectation.fulfill() 28 | } 29 | let coordinator = sut.makeCoordinator() 30 | 31 | coordinator.onAction(self) 32 | waitForExpectations(timeout: 1) { error in 33 | if error != nil { 34 | XCTFail("Action passed in PayPalButton.Representable was never called.") 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /UnitTests/PaymentButtonsTests/PayPalCreditButton_Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PaymentButtons 3 | 4 | class PayPalCreditButton_Tests: XCTestCase { 5 | 6 | // MARK: - PayPalPayCreditButton for UIKit 7 | 8 | func testInit_whenPayPalCreditButtonCreated_hasUIImageFromAssets() { 9 | let sut = PayPalCreditButton() 10 | XCTAssertEqual(sut.imageView?.image, UIImage(named: "PayPalCreditLogo")) 11 | } 12 | 13 | func testInit_whenPayPalCreditButtonCreated_hasDefaultUIValues() { 14 | let sut = PayPalCreditButton() 15 | XCTAssertEqual(sut.edges, PaymentButtonEdges.softEdges) 16 | XCTAssertEqual(sut.size, PaymentButtonSize.collapsed) 17 | XCTAssertEqual(sut.color, PaymentButtonColor.darkBlue) 18 | XCTAssertNil(sut.insets) 19 | XCTAssertNil(sut.label) 20 | } 21 | 22 | // MARK: - PayPalPayCreditButton.Representable for SwiftUI 23 | 24 | func testMakeCoordinator_whenOnActionIsCalled_executesActionPassedInInitializer() { 25 | let expectation = expectation(description: "Action is called") 26 | let sut = PayPalCreditButton.Representable { 27 | expectation.fulfill() 28 | } 29 | let coordinator = sut.makeCoordinator() 30 | 31 | coordinator.onAction(self) 32 | waitForExpectations(timeout: 1) { error in 33 | if error != nil { 34 | XCTFail("Action passed in PayPalCreditButton.Representable was never called.") 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /UnitTests/PaymentButtonsTests/PayPalPayLaterButton_Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PaymentButtons 3 | 4 | class PayPalPayLaterButton_Tests: XCTestCase { 5 | 6 | // MARK: - PayPalPayLaterButton for UIKit 7 | 8 | func testInit_whenPayPalPayLaterButtonCreated_hasDefaultUIValues() { 9 | let sut = PayPalPayLaterButton() 10 | XCTAssertEqual(sut.edges, PaymentButtonEdges.softEdges) 11 | XCTAssertEqual(sut.size, PaymentButtonSize.collapsed) 12 | XCTAssertEqual(sut.color, PaymentButtonColor.gold) 13 | XCTAssertEqual(sut.label, .payLater) 14 | XCTAssertNil(sut.insets) 15 | } 16 | 17 | // MARK: - PayPalPayLaterButton.Representable for SwiftUI 18 | 19 | func testMakeCoordinator_whenOnActionIsCalled_executesActionPassedInInitializer() { 20 | let expectation = expectation(description: "Action is called") 21 | let sut = PayPalPayLaterButton.Representable { 22 | expectation.fulfill() 23 | } 24 | let coordinator = sut.makeCoordinator() 25 | 26 | coordinator.onAction(self) 27 | waitForExpectations(timeout: 1) { error in 28 | if error != nil { 29 | XCTFail("Action passed in PayPalPayLaterButton.Representable was never called.") 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /UnitTests/PaymentsCoreTests/AnalyticsService_Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CorePayments 3 | @testable import TestShared 4 | 5 | class AnalyticsService_Tests: XCTestCase { 6 | 7 | // MARK: - Helper properties 8 | 9 | var sut: AnalyticsService! 10 | var mockTrackingEventsAPI: MockTrackingEventsAPI! 11 | var coreConfig = CoreConfig(clientID: "some-client-id", environment: .sandbox) 12 | 13 | // MARK: - Test lifecycle 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | mockTrackingEventsAPI = MockTrackingEventsAPI(coreConfig: coreConfig) 19 | sut = AnalyticsService(coreConfig: coreConfig, orderID: "some-order-id", trackingEventsAPI: mockTrackingEventsAPI) 20 | } 21 | 22 | // MARK: - sendEvent() 23 | 24 | func testSendEvent_sendsAppropriateAnalyticsEventData() async { 25 | await sut.performEventRequest("some-event", correlationID: "fake-correlation-id") 26 | 27 | XCTAssertEqual(mockTrackingEventsAPI.capturedAnalyticsEventData?.eventName, "some-event") 28 | XCTAssertEqual(mockTrackingEventsAPI.capturedAnalyticsEventData?.clientID, "some-client-id") 29 | XCTAssertEqual(mockTrackingEventsAPI.capturedAnalyticsEventData?.orderID, "some-order-id") 30 | XCTAssertEqual(mockTrackingEventsAPI.capturedAnalyticsEventData?.correlationID, "fake-correlation-id") 31 | } 32 | 33 | func testSendEvent_whenLive_sendsAppropriateEnvName() async { 34 | let sut = AnalyticsService( 35 | coreConfig: CoreConfig(clientID: "some-client-id", environment: .live), 36 | orderID: "some-order-id", 37 | trackingEventsAPI: mockTrackingEventsAPI 38 | ) 39 | 40 | await sut.performEventRequest("some-event") 41 | 42 | XCTAssertEqual(mockTrackingEventsAPI.capturedAnalyticsEventData?.environment, "live") 43 | } 44 | 45 | func testSendEvent_whenSandbox_sendsAppropriateEnvName() async { 46 | await sut.performEventRequest("some-event") 47 | 48 | XCTAssertEqual(mockTrackingEventsAPI.capturedAnalyticsEventData?.environment, "sandbox") 49 | } 50 | 51 | func testSendEvent_whenAPIRequestFails_logsErrorToConsole() { 52 | // We currently have no way to validate our console logging 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /UnitTests/PaymentsCoreTests/Environment_Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CorePayments 3 | 4 | class Environment_Tests: XCTestCase { 5 | 6 | func testEnvironment_sandboxURL_matchesExpectation() throws { 7 | let expectedUrlString = "https://api-m.sandbox.paypal.com" 8 | XCTAssertEqual(Environment.sandbox.baseURL.absoluteString, expectedUrlString) 9 | } 10 | 11 | func testEnvironment_liveURL_matchesExpectation() throws { 12 | let expectedUrlString = "https://api-m.paypal.com" 13 | XCTAssertEqual(Environment.live.baseURL.absoluteString, expectedUrlString) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /UnitTests/PaymentsCoreTests/HTTPResponse_Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CorePayments 3 | 4 | class HTTPResponse_Tests: XCTestCase { 5 | 6 | func testIsSuccessful_whenStatusBelow200_returnsFalse() { 7 | let sut = HTTPResponse(status: 199, body: nil) 8 | XCTAssertFalse(sut.isSuccessful) 9 | } 10 | 11 | func testIsSuccessful_whenStatusAboveOrEqualTo300_returnsFalse() { 12 | let sut = HTTPResponse(status: 300, body: nil) 13 | XCTAssertFalse(sut.isSuccessful) 14 | 15 | let sutB = HTTPResponse(status: 500, body: nil) 16 | XCTAssertFalse(sutB.isSuccessful) 17 | } 18 | 19 | func testIsSuccessful_whenStatusWithinRange_returnsTrue() { 20 | let sut = HTTPResponse(status: 200, body: nil) 21 | XCTAssertTrue(sut.isSuccessful) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /UnitTests/PaymentsCoreTests/Mocks/MockTrackingEventsAPI.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import CorePayments 3 | 4 | class MockTrackingEventsAPI: TrackingEventsAPI { 5 | 6 | var stubHTTPResponse: HTTPResponse? 7 | var stubError: Error? 8 | 9 | var capturedAnalyticsEventData: AnalyticsEventData? 10 | 11 | override func sendEvent(with analyticsEventData: AnalyticsEventData) async throws -> HTTPResponse { 12 | capturedAnalyticsEventData = analyticsEventData 13 | 14 | if let stubError { 15 | throw stubError 16 | } 17 | 18 | if let stubHTTPResponse { 19 | return stubHTTPResponse 20 | } 21 | 22 | throw CoreSDKError(code: 0, domain: "", errorDescription: "Stubbed responses not implemented for this mock.") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /UnitTests/TestShared/FailingJSONEncoder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import CorePayments 3 | 4 | enum TestError: Error { 5 | case encodeError 6 | } 7 | 8 | class FailingJSONEncoder: JSONEncoder { 9 | 10 | override func encode(_ value: T) throws -> Data where T: Encodable { 11 | throw TestError.encodeError 12 | } 13 | } 14 | 15 | class FailingJSONDecoder: JSONDecoder { 16 | 17 | override func decode(_ type: T.Type, from data: Data) throws -> T where T: Decodable { 18 | throw CoreSDKError( 19 | code: 1, 20 | domain: "JSONDecoder.FakeDomain", 21 | errorDescription: "Stub message from JSONDecoder." 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /UnitTests/TestShared/FakeRequests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CorePayments 3 | 4 | public struct FakeResponse: Decodable { 5 | 6 | var fakeParam: String 7 | } 8 | 9 | public struct FakeRequest: Encodable { 10 | 11 | var fakeParam: String 12 | } 13 | -------------------------------------------------------------------------------- /UnitTests/TestShared/MockHTTP.swift: -------------------------------------------------------------------------------- 1 | @testable import CorePayments 2 | import Foundation 3 | 4 | class MockHTTP: HTTP { 5 | 6 | var stubHTTPResponse: HTTPResponse? 7 | var stubHTTPError: Error? 8 | 9 | var capturedHTTPRequest: HTTPRequest? 10 | 11 | override func performRequest(_ httpRequest: HTTPRequest) async throws -> HTTPResponse { 12 | capturedHTTPRequest = httpRequest 13 | 14 | if let stubHTTPError { 15 | throw stubHTTPError 16 | } 17 | 18 | if let stubHTTPResponse { 19 | return stubHTTPResponse 20 | } 21 | 22 | throw CoreSDKError(code: 0, domain: "", errorDescription: "Stubbed responses not implemented for this mock.") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /UnitTests/TestShared/MockNetworkingClient.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import CorePayments 3 | 4 | class MockNetworkingClient: NetworkingClient { 5 | 6 | var stubHTTPResponse: HTTPResponse? 7 | var stubHTTPError: Error? 8 | 9 | var capturedRESTRequest: RESTRequest? 10 | var capturedGraphQLRequest: GraphQLRequest? 11 | 12 | override func fetch(request: RESTRequest) async throws -> HTTPResponse { 13 | capturedRESTRequest = request 14 | 15 | if let stubHTTPError { 16 | throw stubHTTPError 17 | } 18 | 19 | if let stubHTTPResponse { 20 | return stubHTTPResponse 21 | } 22 | 23 | throw CoreSDKError(code: 0, domain: "", errorDescription: "Stubbed responses not implemented for this mock.") 24 | } 25 | 26 | override func fetch(request: GraphQLRequest) async throws -> HTTPResponse { 27 | capturedGraphQLRequest = request 28 | 29 | if let stubHTTPError { 30 | throw stubHTTPError 31 | } 32 | 33 | if let stubHTTPResponse { 34 | return stubHTTPResponse 35 | } 36 | 37 | throw CoreSDKError(code: 0, domain: "", errorDescription: "Stubbed responses not implemented for this mock.") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /UnitTests/TestShared/MockQuededURLSession.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import CorePayments 3 | 4 | class MockQuededURLSession: URLSessionProtocol { 5 | 6 | private var quededResponses: [MockResponse] = [] 7 | 8 | func performRequest(with urlRequest: URLRequest) async throws -> (Data, URLResponse) { 9 | if quededResponses.isEmpty { 10 | fatalError("No responses found") 11 | } 12 | if let response = quededResponses.first { 13 | quededResponses.remove(at: 0) 14 | switch response { 15 | case .success(let success): 16 | return (success.data, success.urlResponse) 17 | 18 | case .failure(let error): 19 | throw error 20 | } 21 | } else { 22 | fatalError("Response can not be null") 23 | } 24 | } 25 | 26 | func addResponse(_ response: MockResponse) { 27 | quededResponses.append(response) 28 | } 29 | 30 | func clear() { 31 | quededResponses.removeAll() 32 | } 33 | } 34 | 35 | enum MockResponse { 36 | 37 | struct Success { 38 | 39 | let data: Data 40 | let urlResponse: URLResponse 41 | } 42 | 43 | case success(Success) 44 | case failure(Error) 45 | } 46 | -------------------------------------------------------------------------------- /UnitTests/TestShared/MockURLSession.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import CorePayments 3 | 4 | class MockURLSession: URLSessionProtocol { 5 | 6 | var cannedError: Error? 7 | var cannedURLResponse: URLResponse? 8 | var cannedJSONData: String? 9 | 10 | var capturedURLRequest: URLRequest? 11 | 12 | func performRequest(with urlRequest: URLRequest) async throws -> (Data, URLResponse) { 13 | capturedURLRequest = urlRequest 14 | 15 | if let error = cannedError { 16 | throw error 17 | } else { 18 | let data = cannedJSONData?.data(using: String.Encoding.utf8) ?? Data() 19 | let urlResponse = cannedURLResponse ?? HTTPURLResponse() 20 | return (data, urlResponse) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /UnitTests/TestShared/MockViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import AuthenticationServices 3 | 4 | class MockViewController: UIViewController, ASWebAuthenticationPresentationContextProviding { 5 | 6 | func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { 7 | ASPresentationAnchor() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /UnitTests/TestShared/MockWebAuthenticationSession.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AuthenticationServices 3 | @testable import CorePayments 4 | 5 | class MockWebAuthenticationSession: WebAuthenticationSession { 6 | 7 | var cannedResponseURL: URL? 8 | var cannedErrorResponse: Error? 9 | var cannedDidDisplayResult = true 10 | var lastLaunchedURL: URL? 11 | 12 | override func start( 13 | url: URL, 14 | context: ASWebAuthenticationPresentationContextProviding, 15 | sessionDidDisplay: @escaping (Bool) -> Void, 16 | sessionDidComplete: @escaping (URL?, Error?) -> Void 17 | ) { 18 | lastLaunchedURL = url 19 | sessionDidDisplay(cannedDidDisplayResult) 20 | sessionDidComplete(cannedResponseURL, cannedErrorResponse) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scripts/swiftlint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if test -d "/opt/homebrew/bin/"; then 4 | PATH="/opt/homebrew/bin/:${PATH}" 5 | fi 6 | 7 | export PATH 8 | 9 | if which swiftlint >/dev/null; then 10 | swiftlint 11 | else 12 | echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" 13 | fi 14 | --------------------------------------------------------------------------------