├── .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 |
--------------------------------------------------------------------------------