├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md ├── actions │ ├── decode_signing_key_action │ │ └── action.yml │ ├── publish_all_modules │ │ └── action.yml │ ├── set_github_user │ │ └── action.yml │ └── unit_test_module │ │ └── action.yml └── workflows │ ├── build.yml │ ├── release.yml │ ├── release_demo.yml │ ├── release_snapshot.yml │ ├── static_analysis.yml │ └── tests.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml └── misc.xml ├── CHANGELOG.md ├── CardPayments ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── paypal │ │ └── android │ │ └── cardpayments │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── paypal │ │ │ └── android │ │ │ └── cardpayments │ │ │ ├── ApproveOrderMetadata.kt │ │ │ ├── Card.kt │ │ │ ├── CardApproveOrderCallback.kt │ │ │ ├── CardApproveOrderResult.kt │ │ │ ├── CardAuthChallenge.kt │ │ │ ├── CardAuthLauncher.kt │ │ │ ├── CardClient.kt │ │ │ ├── CardError.kt │ │ │ ├── CardErrorCode.kt │ │ │ ├── CardFinishApproveOrderResult.kt │ │ │ ├── CardFinishVaultResult.kt │ │ │ ├── CardPresentAuthChallengeResult.kt │ │ │ ├── CardRequest.kt │ │ │ ├── CardRequestFactory.kt │ │ │ ├── CardResponseParser.kt │ │ │ ├── CardVaultCallback.kt │ │ │ ├── CardVaultRequest.kt │ │ │ ├── CardVaultResult.kt │ │ │ ├── DataVaultPaymentMethodTokensAPI.kt │ │ │ ├── UpdateSetupTokenResult.kt │ │ │ ├── analytics │ │ │ ├── ApproveOrderEvent.kt │ │ │ ├── CardAnalytics.kt │ │ │ └── VaultEvent.kt │ │ │ ├── api │ │ │ ├── CheckoutOrdersAPI.kt │ │ │ └── ConfirmPaymentSourceResult.kt │ │ │ ├── model │ │ │ ├── Amount.kt │ │ │ ├── AuthenticationResult.kt │ │ │ ├── Payee.kt │ │ │ ├── PaymentSource.kt │ │ │ └── PurchaseUnit.kt │ │ │ └── threedsecure │ │ │ ├── SCA.kt │ │ │ └── ThreeDSecureResult.kt │ └── res │ │ └── raw │ │ └── graphql_query_update_setup_token.graphql │ └── test │ ├── java │ └── com │ │ └── paypal │ │ └── android │ │ └── cardpayments │ │ ├── ApproveOrderMetadataUnitTest.kt │ │ ├── CardAuthLauncherUnitTest.kt │ │ ├── CardClientUnitTest.kt │ │ ├── CardRequestFactoryUnitTest.kt │ │ ├── CardRequestParserUnitTest.kt │ │ ├── CheckoutOrdersAPIUnitTest.kt │ │ └── DataVaultPaymentMethodTokensAPIUnitTest.kt │ └── resources │ └── robolectric.properties ├── CorePayments ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── paypal │ │ └── android │ │ └── corepayments │ │ └── HttpIntegrationTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── paypal │ │ └── android │ │ └── corepayments │ │ ├── APIClientError.kt │ │ ├── APIRequest.kt │ │ ├── Address.kt │ │ ├── Base64.kt │ │ ├── BrowserSwitchRequestCodes.kt │ │ ├── CoreConfig.kt │ │ ├── CoreCoroutineExceptionHandler.kt │ │ ├── Environment.kt │ │ ├── Http.kt │ │ ├── HttpMethod.kt │ │ ├── HttpRequest.kt │ │ ├── HttpResponse.kt │ │ ├── HttpResponseParser.kt │ │ ├── LoadRawResourceResult.kt │ │ ├── OrderErrorDetail.kt │ │ ├── OrderStatus.kt │ │ ├── PayPalSDKError.kt │ │ ├── PayPalSDKErrorCode.kt │ │ ├── PaymentsJSON.kt │ │ ├── ResourceLoader.kt │ │ ├── RestClient.kt │ │ ├── TrackingEventsAPI.kt │ │ ├── analytics │ │ ├── AnalyticsEventData.kt │ │ ├── AnalyticsService.kt │ │ ├── DeviceData.kt │ │ └── DeviceInspector.kt │ │ └── graphql │ │ ├── GraphQLClient.kt │ │ ├── GraphQLError.kt │ │ ├── GraphQLExtension.kt │ │ └── GraphQLResult.kt │ └── test │ ├── java │ └── com │ │ └── paypal │ │ └── android │ │ └── corepayments │ │ ├── CoreConfigUnitTest.kt │ │ ├── EnvironmentUnitTest.kt │ │ ├── HttpResponseParserUnitTest.kt │ │ ├── HttpUnitTest.kt │ │ ├── PaymentsJSONUnitTest.kt │ │ ├── RestClientUnitTest.kt │ │ ├── TestUtils.kt │ │ ├── TrackingEventsAPIUnitTest.kt │ │ ├── analytics │ │ ├── AnalyticsServiceTest.kt │ │ └── DeviceInspectorUnitTest.kt │ │ └── graphql │ │ └── GraphQLClientUnitTest.kt │ └── resources │ └── robolectric.properties ├── Demo ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── paypal │ │ └── android │ │ └── CardTest.kt │ ├── androidTestShared │ └── java │ │ └── com │ │ └── paypal │ │ └── android │ │ └── testutils │ │ └── AppDriver.kt │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── paypal │ │ │ └── android │ │ │ ├── MainActivity.kt │ │ │ ├── MainApplication.kt │ │ │ ├── api │ │ │ ├── model │ │ │ │ ├── CardPaymentToken.kt │ │ │ │ ├── CardSetupToken.kt │ │ │ │ ├── ClientId.kt │ │ │ │ ├── Order.kt │ │ │ │ ├── OrderIntent.kt │ │ │ │ ├── PayPalPaymentToken.kt │ │ │ │ └── PayPalSetupToken.kt │ │ │ └── services │ │ │ │ ├── MerchantIntegration.kt │ │ │ │ ├── SDKSampleServerAPI.kt │ │ │ │ ├── SDKSampleServerException.kt │ │ │ │ └── SDKSampleServerResult.kt │ │ │ ├── di │ │ │ └── NetworkModule.kt │ │ │ ├── models │ │ │ ├── OrderRequest.kt │ │ │ └── TestCard.kt │ │ │ ├── ui │ │ │ ├── DemoApp.kt │ │ │ ├── DemoAppDestinations.kt │ │ │ ├── approveorder │ │ │ │ ├── ApproveOrderForm.kt │ │ │ │ ├── ApproveOrderUiState.kt │ │ │ │ ├── ApproveOrderView.kt │ │ │ │ ├── ApproveOrderViewModel.kt │ │ │ │ ├── CardFormatter.kt │ │ │ │ ├── CardNumberVisualTransformation.kt │ │ │ │ ├── CardType.kt │ │ │ │ ├── DateString.kt │ │ │ │ ├── DateVisualTransformation.kt │ │ │ │ ├── OrderInfo.kt │ │ │ │ └── SetupTokenInfo.kt │ │ │ ├── features │ │ │ │ ├── Feature.kt │ │ │ │ └── FeaturesView.kt │ │ │ ├── paypalbuttons │ │ │ │ ├── ButtonFundingType.kt │ │ │ │ ├── PayPalButtonColorOptionList.kt │ │ │ │ ├── PayPalButtonFundingTypeOptionList.kt │ │ │ │ ├── PayPalButtonLabelOptionList.kt │ │ │ │ ├── PayPalButtonsUiState.kt │ │ │ │ ├── PayPalButtonsView.kt │ │ │ │ ├── PayPalButtonsViewModel.kt │ │ │ │ ├── PayPalCreditButtonColorOptionList.kt │ │ │ │ ├── PaymentButtonShapeOptionList.kt │ │ │ │ └── PaymentButtonSizeOptionList.kt │ │ │ ├── paypalstaticbuttons │ │ │ │ └── PayPalStaticButtonsView.kt │ │ │ ├── paypalweb │ │ │ │ ├── PayPalWebCheckoutResultView.kt │ │ │ │ ├── PayPalWebUiState.kt │ │ │ │ ├── PayPalWebView.kt │ │ │ │ ├── PayPalWebViewModel.kt │ │ │ │ └── StartPayPalWebCheckoutForm.kt │ │ │ ├── paypalwebvault │ │ │ │ ├── PayPalWebVaultUiState.kt │ │ │ │ ├── PayPalWebVaultView.kt │ │ │ │ └── PayPalWebVaultViewModel.kt │ │ │ ├── selectcard │ │ │ │ ├── SelectCardView.kt │ │ │ │ └── SelectCardViewModel.kt │ │ │ └── vaultcard │ │ │ │ ├── VaultCardUiState.kt │ │ │ │ ├── VaultCardView.kt │ │ │ │ └── VaultCardViewModel.kt │ │ │ ├── uishared │ │ │ ├── components │ │ │ │ ├── ActionButton.kt │ │ │ │ ├── ActionButtonColumn.kt │ │ │ │ ├── BooleanOptionList.kt │ │ │ │ ├── CardForm.kt │ │ │ │ ├── CardPaymentTokenView.kt │ │ │ │ ├── CardResultView.kt │ │ │ │ ├── CardSetupTokenView.kt │ │ │ │ ├── CardVaultResultView.kt │ │ │ │ ├── CreateOrderForm.kt │ │ │ │ ├── CreateOrderWithVaultOptionForm.kt │ │ │ │ ├── DemoAppTopBar.kt │ │ │ │ ├── EnumOptionList.kt │ │ │ │ ├── ErrorView.kt │ │ │ │ ├── InfoColumn.kt │ │ │ │ ├── IntSlider.kt │ │ │ │ ├── OptionList.kt │ │ │ │ ├── OrderView.kt │ │ │ │ ├── PayPalPaymentTokenView.kt │ │ │ │ ├── PayPalSetupTokenView.kt │ │ │ │ ├── PropertyView.kt │ │ │ │ ├── StepHeader.kt │ │ │ │ └── UriView.kt │ │ │ ├── effects │ │ │ │ └── NavDestinationChangeDisposableEffect.kt │ │ │ ├── enums │ │ │ │ └── StoreInVaultOption.kt │ │ │ └── state │ │ │ │ ├── ActionState.kt │ │ │ │ └── CompletedActionState.kt │ │ │ ├── usecase │ │ │ ├── CompleteOrderUseCase.kt │ │ │ ├── CreateCardPaymentTokenUseCase.kt │ │ │ ├── CreateCardSetupTokenUseCase.kt │ │ │ ├── CreateOrderUseCase.kt │ │ │ ├── CreatePayPalPaymentTokenUseCase.kt │ │ │ ├── CreatePayPalSetupTokenUseCase.kt │ │ │ ├── GetClientIdUseCase.kt │ │ │ └── GetSetupTokenUseCase.kt │ │ │ └── utils │ │ │ ├── ContextExt.kt │ │ │ ├── OnLifecycleOwnerResumeEffect.kt │ │ │ ├── OnNewIntentEffect.kt │ │ │ └── UIConstants.kt │ ├── play │ │ ├── default-language.txt │ │ ├── listings │ │ │ └── en-US │ │ │ │ └── title.txt │ │ └── release-notes │ │ │ └── en-US │ │ │ └── internal.txt │ └── res │ │ ├── drawable │ │ └── chevron.xml │ │ ├── layout │ │ └── pay_later_button_test_layout.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ └── network_security_config.xml │ └── test │ └── java │ └── com │ └── paypal │ └── android │ └── ExampleUnitTest.kt ├── FraudProtection ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── libs │ └── android-magnessdk-5.5.1.jar ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── paypal │ │ └── android │ │ └── fraudprotection │ │ ├── CoreConfigMagnesExt.kt │ │ ├── PayPalDataCollector.kt │ │ ├── PayPalDataCollectorRequest.kt │ │ ├── SharedPreferenceUtils.kt │ │ └── UUIDHelper.kt │ └── test │ └── java │ └── com │ └── paypal │ └── android │ └── fraudprotection │ ├── PayPalDataCollectorUnitTest.kt │ └── UUIDHelperUnitTest.kt ├── LICENSE ├── MOBILE_CHECKOUT_MIGRATION_GUIDE.md ├── PayPalWebPayments ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── paypal │ │ └── android │ │ └── paypalwebpayments │ │ ├── PayPalPresentAuthChallengeResult.kt │ │ ├── PayPalWebCheckoutClient.kt │ │ ├── PayPalWebCheckoutFinishStartResult.kt │ │ ├── PayPalWebCheckoutFinishVaultResult.kt │ │ ├── PayPalWebCheckoutFundingSource.kt │ │ ├── PayPalWebCheckoutRequest.kt │ │ ├── PayPalWebLauncher.kt │ │ ├── PayPalWebVaultRequest.kt │ │ ├── analytics │ │ ├── CheckoutEvent.kt │ │ ├── PayPalWebAnalytics.kt │ │ └── VaultEvent.kt │ │ └── errors │ │ ├── PayPalWebCheckoutError.kt │ │ └── PayPalWebCheckoutErrorCode.kt │ └── test │ ├── java │ └── com │ │ └── paypal │ │ └── android │ │ └── paypalwebpayments │ │ ├── PayPalWebCheckoutClientUnitTest.kt │ │ ├── PayPalWebCheckoutRequestUnitTest.kt │ │ └── PayPalWebLauncherUnitTest.kt │ └── resources │ └── robolectric.properties ├── PaymentButtons ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── paypal │ │ └── android │ │ └── paymentbuttons │ │ ├── PayLaterButton.kt │ │ ├── PayPalButton.kt │ │ ├── PayPalCreditButton.kt │ │ ├── PaymentButton.kt │ │ ├── PaymentButtonColor.kt │ │ ├── PaymentButtonShape.kt │ │ ├── PaymentButtonSize.kt │ │ └── error │ │ └── Exceptions.kt │ └── res │ ├── color │ ├── paypal_black.xml │ ├── paypal_blue.xml │ ├── paypal_dark_blue.xml │ ├── paypal_gold.xml │ ├── paypal_silver.xml │ └── paypal_white.xml │ ├── drawable │ ├── logo_paypal_color.xml │ ├── logo_paypal_monochrome.xml │ ├── wordmark_paypal_color.xml │ ├── wordmark_paypal_credit_color.xml │ ├── wordmark_paypal_credit_monochrome.xml │ └── wordmark_paypal_monochrome.xml │ ├── font │ └── paypalopen_regular.otf │ ├── layout │ └── paypal_ui_payment_button_view.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ └── strings.xml ├── README.md ├── Venmo ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── paypal │ │ └── android │ │ └── venmo │ │ └── ExampleInstrumentedTest.kt │ ├── main │ └── AndroidManifest.xml │ └── test │ └── java │ └── com │ └── paypal │ └── android │ └── venmo │ └── ExampleUnitTest.kt ├── adr ├── 1-remove-graphql-generics │ ├── figure-graph-ql-client.png │ ├── figure-payments-sdk-architecture.png │ ├── figure-query-abstract-base-class.png │ └── index.md └── 2-remove-core-api-and-http-request-factories │ ├── figure-card-client-example.png │ ├── figure-deep-module-vs-shallow-module.png │ ├── figure-multi-api-uml.png │ └── index.md ├── build.gradle ├── codecov.yml ├── detekt └── detekt-config.yml ├── gradle.properties ├── gradle ├── gradle-publish.gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lint.xml ├── settings.gradle └── v2_MIGRATION_GUIDE.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @paypal/sdks-android 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 Android SDK Version 7 | placeholder: "e.g. 1.12.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: Android Version & Device 22 | placeholder: "e.g Google Pixel 6a - Android 12.0" 23 | validations: 24 | required: false 25 | - type: textarea 26 | attributes: 27 | label: PayPal dependencies 28 | placeholder: | 29 | 30 | 31 | 32 | validations: 33 | required: true 34 | - type: textarea 35 | attributes: 36 | label: Describe the bug 37 | description: Description of what the bug is. Please include as many details as possible. 38 | validations: 39 | required: true 40 | - type: textarea 41 | attributes: 42 | label: To reproduce 43 | description: Steps to reproduce the behavior. 44 | placeholder: | 45 | 1. Go to '...' 46 | 2. Click on '....' 47 | 3. See error 48 | validations: 49 | required: true 50 | - type: textarea 51 | attributes: 52 | label: Expected behavior 53 | description: Description of what you expected to happen. 54 | validations: 55 | required: true 56 | - type: textarea 57 | attributes: 58 | label: Screenshots 59 | description: If applicable, add screenshots to help explain your problem. 60 | placeholder: | 61 | Do not reveal sensitive data. For example, credit card numbers & customer credentials. 62 | validations: 63 | required: false 64 | -------------------------------------------------------------------------------- /.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 | Thank you for your contribution to PayPal. 2 | 3 | > Before submitting this PR, note that we cannot accept language translation PRs. We have a dedicated localization team to provide the translations. If there is an error in a specific translation, you may open an issue and we will escalate it to the localization team. 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 | - 17 | -------------------------------------------------------------------------------- /.github/actions/decode_signing_key_action/action.yml: -------------------------------------------------------------------------------- 1 | name: Decode signing key 2 | description: 'Decodes gpg key into file' 3 | inputs: 4 | signing_key_file: 5 | description: 'Signing key file' 6 | required: true 7 | default: '' 8 | signing_file_path: 9 | description: 'Signing file path' 10 | required: true 11 | default: '' 12 | runs: 13 | using: "composite" 14 | steps: 15 | - run: | 16 | echo "${{inputs.signing_key_file}}" > ~/secretKey.gpg.b64 17 | base64 -d ~/secretKey.gpg.b64 > ${{ inputs.signing_file_path }} 18 | shell: bash -------------------------------------------------------------------------------- /.github/actions/publish_all_modules/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Publish All Modules' 2 | description: 'Publishes all modules' 3 | inputs: 4 | sonatype_usr: 5 | description: 'Sonatype user' 6 | required: true 7 | default: '' 8 | sonatype_pwd: 9 | description: 'Sonatype password' 10 | required: true 11 | default: '' 12 | signing_key_id: 13 | description: 'Signing key id' 14 | required: true 15 | default: '' 16 | signing_key_pwd: 17 | description: 'Signing key password' 18 | required: true 19 | default: '' 20 | signing_key_file: 21 | description: 'Signing key file' 22 | required: true 23 | default: '' 24 | runs: 25 | using: "composite" 26 | steps: 27 | - run: | 28 | ./gradlew --stacktrace clean publishToSonatype closeAndReleaseSonatypeStagingRepository 29 | shell: bash 30 | env: 31 | SONATYPE_NEXUS_USERNAME: ${{ inputs.sonatype_usr }} 32 | SONATYPE_NEXUS_PASSWORD: ${{ inputs.sonatype_pwd }} 33 | SIGNING_KEY_ID: ${{ inputs.signing_key_id }} 34 | SIGNING_KEY_PASSWORD: ${{ inputs.signing_key_pwd }} 35 | SIGNING_KEY_FILE: ${{ inputs.signing_key_file }} -------------------------------------------------------------------------------- /.github/actions/set_github_user/action.yml: -------------------------------------------------------------------------------- 1 | name: Set github user 2 | description: 'Sets github user for a particular workflow' 3 | runs: 4 | using: "composite" 5 | steps: 6 | - run: | 7 | git config user.name paypalsdk 8 | git config user.email sdks@paypal.com 9 | shell: bash -------------------------------------------------------------------------------- /.github/actions/unit_test_module/action.yml: -------------------------------------------------------------------------------- 1 | name: Unit Test Module 2 | description: 'Runs unit test for specified module' 3 | inputs: 4 | module: 5 | description: 'Module' 6 | required: true 7 | default: '' 8 | runs: 9 | using: "composite" 10 | steps: 11 | - run: ./gradlew --stacktrace :${{ inputs.module }}:testRelease 12 | shell: bash -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [pull_request, workflow_dispatch] 3 | concurrency: 4 | group: build-${{ github.event.number }} 5 | cancel-in-progress: true 6 | jobs: 7 | build_aar: 8 | name: Build 9 | runs-on: ubuntu-latest 10 | env: 11 | SIGNING_KEY_FILE_PATH: /home/runner/secretKey.gpg 12 | steps: 13 | - name: Checkout Repository 14 | uses: actions/checkout@v4 15 | - name: Set up Java 16 | uses: actions/setup-java@v4 17 | with: 18 | java-version: '17' 19 | distribution: 'microsoft' 20 | #After decoding the secret key, place the file in ~ /. Gradle/ secring.gpg 21 | - name: Decode Signing Key 22 | uses: ./.github/actions/decode_signing_key_action 23 | with: 24 | signing_key_file: ${{ secrets.SIGNING_KEY_FILE }} 25 | signing_file_path: ${{ env.SIGNING_KEY_FILE_PATH }} 26 | - name: Assemble 27 | run: ./gradlew --stacktrace assemble -x :Demo:assemble # we exclude Demo module in assemble 28 | env: 29 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} 30 | SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} 31 | SIGNING_KEY_FILE: ${{ env.SIGNING_KEY_FILE_PATH }} 32 | -------------------------------------------------------------------------------- /.github/workflows/release_demo.yml: -------------------------------------------------------------------------------- 1 | name: Release Demo 2 | on: workflow_dispatch 3 | env: 4 | DEMO_KEYSTORE_FILE: /home/runner/demo_keystore.keystore 5 | DEMO_GCP_SERVICE_ACCOUNT_CREDENTIALS_FILE: /home/runner/demo_gcp_service_account_credentials.json 6 | jobs: 7 | publish_demo_app: 8 | name: Publish Demo App 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout Repository 12 | uses: actions/checkout@v4 13 | - name: Set up Java 14 | uses: actions/setup-java@v4 15 | with: 16 | java-version: '17' 17 | distribution: 'microsoft' 18 | - name: Decode Demo Keystore 19 | run: | 20 | echo "${{ secrets.DEMO_KEYSTORE_BASE64_ENCODED }}" > ~/demo_keystore.keystore.b64 21 | base64 -d ~/demo_keystore.keystore.b64 > "${DEMO_KEYSTORE_FILE}" 22 | - name: Decode Demo GCP Service Account Credentials 23 | run: | 24 | echo "${{ secrets.DEMO_GCP_SERVICE_ACCOUNT_CREDENTIALS_BASE64_ENCODED }}" > ~/gcp_service_account_credentials.json.b64 25 | base64 -d ~/gcp_service_account_credentials.json.b64 > "${DEMO_GCP_SERVICE_ACCOUNT_CREDENTIALS_FILE}" 26 | - name: Publish Release Bundle 27 | run: ./gradlew publishReleaseBundle 28 | env: 29 | DEMO_KEYSTORE_PASSWORD: ${{ secrets.DEMO_KEYSTORE_PASSWORD }} 30 | DEMO_KEY_ALIAS: ${{ secrets.DEMO_KEY_ALIAS }} 31 | DEMO_KEY_PASSWORD: ${{ secrets.DEMO_KEY_PASSWORD }} 32 | bump_demo_app_version_code: 33 | needs: [ publish_demo_app ] 34 | name: Bump Demo App Version 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Checkout Repository 38 | uses: actions/checkout@v4 39 | - name: Set github user 40 | uses: ./.github/actions/set_github_user 41 | - name: Update Version 42 | run: | 43 | VERSION_CODE=$(./gradlew -q getDemoAppVersionCode) 44 | UPDATED_VERSION_CODE=$((${VERSION_CODE} + 1)) 45 | 46 | ./gradlew -PdemoAppVersionCodeParam=${UPDATED_VERSION_CODE} setDemoAppVersionCode 47 | 48 | git add . 49 | git commit -m "Bump demo app version code to ${UPDATED_VERSION_CODE}." 50 | git push origin HEAD -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: [ pull_request, workflow_dispatch ] 3 | concurrency: 4 | group: tests-${{ github.event.number }} 5 | cancel-in-progress: true 6 | jobs: 7 | unit_test_job: 8 | name: Unit Tests 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout Repository 12 | uses: actions/checkout@v4 13 | - name: Set up Java 14 | uses: actions/setup-java@v4 15 | with: 16 | java-version: '17' 17 | distribution: 'microsoft' 18 | - name: Run Unit Tests 19 | run: ./gradlew --stacktrace testDebug 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # Android Studio 3 in .gitignore file. 40 | .idea/ 41 | 42 | 43 | # Keystore files 44 | # Uncomment the following lines if you do not want to check your keystore files in. 45 | #*.jks 46 | #*.keystore 47 | 48 | # External native build folder generated in Android Studio 2.2 and later 49 | .externalNativeBuild 50 | .cxx/ 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | # google-services.json 54 | 55 | # Freeline 56 | freeline.py 57 | freeline/ 58 | freeline_project_description.json 59 | 60 | # fastlane 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | fastlane/test_output 65 | fastlane/readme.md 66 | 67 | # Version control 68 | vcs.xml 69 | 70 | # lint 71 | lint/intermediates/ 72 | lint/generated/ 73 | lint/outputs/ 74 | lint/tmp/ 75 | # lint/reports/ 76 | 77 | # Android Profiling 78 | *.hprof 79 | 80 | # DS_Store 81 | .DS_Store 82 | 83 | # PayPal 84 | paypal.properties 85 | 86 | # vim 87 | *.swp 88 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/.idea/.gitignore -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | AndroidSDK -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CardPayments/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /CardPayments/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/CardPayments/consumer-rules.pro -------------------------------------------------------------------------------- /CardPayments/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /CardPayments/src/androidTest/java/com/paypal/android/cardpayments/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | import junit.framework.TestCase.assertEquals 6 | 7 | import org.junit.Test 8 | import org.junit.runner.RunWith 9 | 10 | /** 11 | * Instrumented test, which will execute on an Android device. 12 | * 13 | * See [testing documentation](http://d.android.com/tools/testing). 14 | */ 15 | @RunWith(AndroidJUnit4::class) 16 | class ExampleInstrumentedTest { 17 | @Test 18 | fun useAppContext() { 19 | // Context of the app under test. 20 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 21 | assertEquals("com.paypal.android.card.test", appContext.packageName) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CardPayments/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/ApproveOrderMetadata.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import com.paypal.android.cardpayments.model.PaymentSource 4 | import com.paypal.android.corepayments.PaymentsJSON 5 | import org.json.JSONObject 6 | 7 | internal data class ApproveOrderMetadata( 8 | val orderId: String, 9 | val paymentSource: PaymentSource? = null 10 | ) { 11 | 12 | companion object { 13 | 14 | const val KEY_ORDER_ID = "order_id" 15 | const val KEY_PAYMENT_SOURCE = "payment_source" 16 | 17 | fun fromJSON(data: JSONObject?): ApproveOrderMetadata? = 18 | data?.let { fromJSON(PaymentsJSON(it)) } 19 | 20 | private fun fromJSON(json: PaymentsJSON): ApproveOrderMetadata { 21 | val orderId = json.getString(KEY_ORDER_ID) 22 | val paymentSource = json.optMapObject(KEY_PAYMENT_SOURCE) { PaymentSource(it) } 23 | return ApproveOrderMetadata(orderId, paymentSource) 24 | } 25 | } 26 | 27 | fun toJSON(): JSONObject = 28 | JSONObject() 29 | .put(KEY_ORDER_ID, orderId) 30 | .putOpt(KEY_PAYMENT_SOURCE, paymentSource?.toJSON()) 31 | } 32 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/Card.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import android.os.Parcelable 4 | import com.paypal.android.corepayments.Address 5 | import kotlinx.parcelize.Parcelize 6 | 7 | /** 8 | * Represents raw credit or debit card data provided by the customer. 9 | */ 10 | @Parcelize 11 | data class Card @JvmOverloads constructor( 12 | 13 | /** 14 | * The primary account number (PAN) for the payment card 15 | */ 16 | var number: String, 17 | 18 | /** 19 | * The 2-digit card expiration month in `MM` format 20 | */ 21 | val expirationMonth: String, 22 | 23 | /** 24 | * The 4-digit card expiration year in `YYYY` format 25 | */ 26 | val expirationYear: String, 27 | 28 | /** 29 | * The three- or four-digit security code of the card. Also known as the CVV, CVC, CVN, CVE, or CID. 30 | */ 31 | var securityCode: String, 32 | 33 | /** 34 | * Optional. The card holder's name as it appears on the card 35 | */ 36 | var cardholderName: String? = null, 37 | 38 | /** 39 | * Optional. The billing address 40 | */ 41 | var billingAddress: Address? = null, 42 | ) : Parcelable 43 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/CardApproveOrderCallback.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import androidx.annotation.MainThread 4 | 5 | fun interface CardApproveOrderCallback { 6 | /** 7 | * Called when the order is approved. 8 | * 9 | * @param result [CardApproveOrderResult] result with details 10 | */ 11 | @MainThread 12 | fun onCardApproveOrderResult(result: CardApproveOrderResult) 13 | } 14 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/CardApproveOrderResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import com.paypal.android.corepayments.PayPalSDKError 4 | 5 | sealed class CardApproveOrderResult { 6 | 7 | data class Success( 8 | val orderId: String, 9 | val status: String? = null, 10 | val didAttemptThreeDSecureAuthentication: Boolean = false 11 | ) : CardApproveOrderResult() 12 | 13 | data class AuthorizationRequired(val authChallenge: CardAuthChallenge) : CardApproveOrderResult() 14 | data class Failure(val error: PayPalSDKError) : CardApproveOrderResult() 15 | } 16 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/CardAuthChallenge.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import android.net.Uri 4 | import android.os.Parcelable 5 | import kotlinx.parcelize.Parcelize 6 | 7 | /** 8 | * Pass this object to [CardClient.presentAuthChallenge] to present an authentication challenge 9 | * that was received in response to a [CardClient.approveOrder] or [CardClient.vault] call. 10 | */ 11 | sealed class CardAuthChallenge { 12 | // Ref: https://stackoverflow.com/a/44420084 13 | internal abstract val url: Uri 14 | internal abstract val returnUrlScheme: String? 15 | 16 | @Parcelize 17 | internal class ApproveOrder( 18 | override val url: Uri, 19 | val request: CardRequest, 20 | override val returnUrlScheme: String? = Uri.parse(request.returnUrl).scheme 21 | ) : CardAuthChallenge(), Parcelable 22 | 23 | @Parcelize 24 | internal class Vault( 25 | override val url: Uri, 26 | val request: CardVaultRequest, 27 | override val returnUrlScheme: String? = Uri.parse(request.returnUrl).scheme 28 | ) : CardAuthChallenge(), Parcelable 29 | } 30 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/CardError.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import com.paypal.android.corepayments.PayPalSDKError 4 | import com.paypal.android.corepayments.graphql.GraphQLError 5 | 6 | internal object CardError { 7 | 8 | // 0. An error from 3DS verification 9 | val threeDSVerificationError = PayPalSDKError( 10 | code = CardErrorCode.THREEDS_VERIFICATION_FAILED.ordinal, 11 | errorDescription = "3DS Verification is returning an error." 12 | ) 13 | 14 | // 1. 15 | val malformedDeepLinkError = PayPalSDKError( 16 | code = CardErrorCode.MALFORMED_DEEPLINK_URL.ordinal, 17 | errorDescription = "Malformed deeplink URL." 18 | ) 19 | 20 | // 2. An unknown error occurred. 21 | val unknownError = PayPalSDKError( 22 | code = CardErrorCode.UNKNOWN.ordinal, 23 | errorDescription = "An unknown error occurred. Contact developer.paypal.com/support." 24 | ) 25 | 26 | // 3. An unknown error occurred. 27 | fun browserSwitchError(cause: Exception) = PayPalSDKError( 28 | code = CardErrorCode.BROWSER_SWITCH.ordinal, 29 | errorDescription = cause.message ?: "Unable to Browser Switch" 30 | ) 31 | 32 | fun updateSetupTokenResponseBodyMissing(errors: List?, correlationId: String?) = PayPalSDKError( 33 | 0, 34 | "Error updating setup token: $errors", 35 | correlationId 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/CardErrorCode.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | internal enum class CardErrorCode { 4 | THREEDS_VERIFICATION_FAILED, 5 | MALFORMED_DEEPLINK_URL, 6 | UNKNOWN, 7 | BROWSER_SWITCH 8 | } 9 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/CardFinishApproveOrderResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import com.paypal.android.corepayments.PayPalSDKError 4 | 5 | sealed class CardFinishApproveOrderResult { 6 | data class Success( 7 | val orderId: String, 8 | val status: String? = null, 9 | val didAttemptThreeDSecureAuthentication: Boolean = false 10 | ) : CardFinishApproveOrderResult() 11 | 12 | data class Failure(val error: PayPalSDKError) : CardFinishApproveOrderResult() 13 | data object Canceled : CardFinishApproveOrderResult() 14 | data object NoResult : CardFinishApproveOrderResult() 15 | } 16 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/CardFinishVaultResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import com.paypal.android.corepayments.PayPalSDKError 4 | 5 | sealed class CardFinishVaultResult { 6 | 7 | data class Success( 8 | val setupTokenId: String, 9 | val status: String? = null, 10 | val didAttemptThreeDSecureAuthentication: Boolean = false 11 | ) : CardFinishVaultResult() 12 | 13 | data class Failure(val error: PayPalSDKError) : CardFinishVaultResult() 14 | 15 | data object Canceled : CardFinishVaultResult() 16 | data object NoResult : CardFinishVaultResult() 17 | } 18 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/CardPresentAuthChallengeResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import com.paypal.android.corepayments.PayPalSDKError 4 | 5 | sealed class CardPresentAuthChallengeResult { 6 | data class Success(val authState: String) : CardPresentAuthChallengeResult() 7 | data class Failure(val error: PayPalSDKError) : CardPresentAuthChallengeResult() 8 | } 9 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/CardRequest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import android.os.Parcelable 4 | import com.paypal.android.cardpayments.threedsecure.SCA 5 | import kotlinx.parcelize.Parcelize 6 | 7 | /** 8 | * A card request to process a card payment. 9 | * 10 | * @property orderId The order ID to to process the payment 11 | * @property card Card used for payment 12 | * @property returnUrl Url to return to app after SCA challenge finishes 13 | * @property sca Specify to always launch 3DS or only when required. Defaults to `SCA.SCA_WHEN_REQUIRED`. 14 | */ 15 | @Parcelize 16 | data class CardRequest @JvmOverloads constructor( 17 | val orderId: String, 18 | val card: Card, 19 | val returnUrl: String, 20 | val sca: SCA = SCA.SCA_WHEN_REQUIRED, 21 | ) : Parcelable 22 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/CardRequestFactory.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import com.paypal.android.corepayments.APIRequest 4 | import com.paypal.android.corepayments.HttpMethod 5 | import org.json.JSONObject 6 | 7 | internal class CardRequestFactory { 8 | 9 | fun createConfirmPaymentSourceRequest(cardRequest: CardRequest): APIRequest { 10 | val card = cardRequest.card 11 | val cardNumber = card.number.replace("\\s".toRegex(), "") 12 | val cardExpiry = "${card.expirationYear}-${card.expirationMonth}" 13 | 14 | val cardJSON = JSONObject() 15 | .put("number", cardNumber) 16 | .put("expiry", cardExpiry) 17 | 18 | card.cardholderName?.let { cardJSON.put("name", it) } 19 | cardJSON.put("security_code", card.securityCode) 20 | 21 | card.billingAddress?.apply { 22 | val billingAddressJSON = JSONObject() 23 | .put("address_line_1", streetAddress) 24 | .put("address_line_2", extendedAddress) 25 | .put("admin_area_1", region) 26 | .put("admin_area_2", locality) 27 | .put("postal_code", postalCode) 28 | .put("country_code", countryCode) 29 | cardJSON.put("billing_address", billingAddressJSON) 30 | } 31 | 32 | val bodyJSON = JSONObject() 33 | val verificationJSON = JSONObject() 34 | .put("method", cardRequest.sca.name) 35 | val attributesJSON = JSONObject() 36 | .put("verification", verificationJSON) 37 | 38 | cardJSON.put("attributes", attributesJSON) 39 | 40 | val returnUrl = cardRequest.returnUrl 41 | val returnURLJSON = JSONObject() 42 | .put("return_url", returnUrl) 43 | .put("cancel_url", returnUrl) // we can set the same url 44 | bodyJSON.put("application_context", returnURLJSON) 45 | 46 | val paymentSourceJSON = JSONObject().put("card", cardJSON) 47 | bodyJSON.put("payment_source", paymentSourceJSON) 48 | 49 | val body = bodyJSON.toString().replace("\\/", "/") 50 | 51 | val path = "v2/checkout/orders/${cardRequest.orderId}/confirm-payment-source" 52 | return APIRequest(path, HttpMethod.POST, body) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/CardVaultCallback.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import androidx.annotation.MainThread 4 | 5 | fun interface CardVaultCallback { 6 | 7 | /** 8 | * Called when a successful vault has occurred. 9 | */ 10 | @MainThread 11 | fun onCardVaultResult(result: CardVaultResult) 12 | } 13 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/CardVaultRequest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | 6 | /** 7 | * @suppress 8 | * 9 | * A vault request to attach a payment method to a setup token. 10 | * 11 | * @property setupTokenId id for the setup token to update. 12 | * @property card card payment source to attach to the setup token. 13 | * @property returnUrl return url for deep linking back into the merchant app after an auth challenge 14 | */ 15 | @Parcelize 16 | data class CardVaultRequest( 17 | val setupTokenId: String, 18 | val card: Card, 19 | val returnUrl: String? = "", 20 | ) : Parcelable 21 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/CardVaultResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import com.paypal.android.corepayments.PayPalSDKError 4 | 5 | sealed class CardVaultResult { 6 | 7 | /** 8 | * A result returned by [CardClient] when an a successful vault occurs. 9 | * 10 | * @param setupTokenId the id for the setup token that was recently updated 11 | * @param status the status of the updated setup token 12 | * @property [didAttemptThreeDSecureAuthentication] 3DS verification was attempted. 13 | * Use v2/checkout/orders/{orderId} in your server to get verification results. 14 | */ 15 | data class Success( 16 | val setupTokenId: String, 17 | val status: String? = null, 18 | val didAttemptThreeDSecureAuthentication: Boolean = false 19 | ) : CardVaultResult() 20 | 21 | data class AuthorizationRequired(val authChallenge: CardAuthChallenge) : CardVaultResult() 22 | data class Failure(val error: PayPalSDKError) : CardVaultResult() 23 | } 24 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/UpdateSetupTokenResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import com.paypal.android.corepayments.PayPalSDKError 4 | 5 | internal sealed class UpdateSetupTokenResult { 6 | 7 | data class Success( 8 | val setupTokenId: String, 9 | val status: String, 10 | val approveHref: String? 11 | ) : UpdateSetupTokenResult() 12 | 13 | data class Failure(val error: PayPalSDKError) : UpdateSetupTokenResult() 14 | } 15 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/analytics/ApproveOrderEvent.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("SpacingAroundParens", "NoMultipleSpaces", "MaxLineLength") 2 | 3 | package com.paypal.android.cardpayments.analytics 4 | 5 | internal enum class ApproveOrderEvent(val value: String) { 6 | // @formatter:off 7 | STARTED( "card-payments:approve-order:started"), 8 | SUCCEEDED("card-payments:approve-order:succeeded"), 9 | FAILED( "card-payments:approve-order:failed"), 10 | 11 | AUTH_CHALLENGE_REQUIRED("card-payments:approve-order:auth-challenge-required"), 12 | 13 | AUTH_CHALLENGE_PRESENTATION_SUCCEEDED("card-payments:approve-order:auth-challenge-presentation:succeeded"), 14 | AUTH_CHALLENGE_PRESENTATION_FAILED( "card-payments:approve-order:auth-challenge-presentation:failed"), 15 | 16 | AUTH_CHALLENGE_SUCCEEDED("card-payments:approve-order:auth-challenge:succeeded"), 17 | AUTH_CHALLENGE_FAILED( "card-payments:approve-order:auth-challenge:failed"), 18 | AUTH_CHALLENGE_CANCELED( "card-payments:approve-order:auth-challenge:canceled"), 19 | // @formatter:on 20 | } 21 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/analytics/CardAnalytics.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments.analytics 2 | 3 | import com.paypal.android.corepayments.analytics.AnalyticsService 4 | 5 | internal class CardAnalytics(private val analyticsService: AnalyticsService) { 6 | 7 | fun notify(event: ApproveOrderEvent, orderId: String?) { 8 | analyticsService.sendAnalyticsEvent(event.value, orderId) 9 | } 10 | 11 | fun notify(event: VaultEvent, setupTokenId: String?) { 12 | analyticsService.sendAnalyticsEvent(event.value, setupTokenId) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/analytics/VaultEvent.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("SpacingAroundParens", "NoMultipleSpaces", "MaxLineLength") 2 | 3 | package com.paypal.android.cardpayments.analytics 4 | 5 | internal enum class VaultEvent(val value: String) { 6 | // @formatter:off 7 | STARTED( "card-payments:vault-wo-purchase:started"), 8 | SUCCEEDED("card-payments:vault-wo-purchase:succeeded"), 9 | FAILED( "card-payments:vault-wo-purchase:failed"), 10 | 11 | AUTH_CHALLENGE_REQUIRED("card-payments:vault-wo-purchase:auth-challenge-required"), 12 | 13 | AUTH_CHALLENGE_PRESENTATION_SUCCEEDED("card-payments:vault-wo-purchase:auth-challenge-presentation:succeeded"), 14 | AUTH_CHALLENGE_PRESENTATION_FAILED( "card-payments:vault-wo-purchase:auth-challenge-presentation:failed"), 15 | 16 | AUTH_CHALLENGE_SUCCEEDED("card-payments:vault-wo-purchase:auth-challenge:succeeded"), 17 | AUTH_CHALLENGE_FAILED( "card-payments:vault-wo-purchase:auth-challenge:failed"), 18 | AUTH_CHALLENGE_CANCELED( "card-payments:vault-wo-purchase:auth-challenge:canceled"), 19 | // @formatter:on 20 | } 21 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/api/CheckoutOrdersAPI.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments.api 2 | 3 | import com.paypal.android.cardpayments.CardRequest 4 | import com.paypal.android.cardpayments.CardRequestFactory 5 | import com.paypal.android.cardpayments.CardResponseParser 6 | import com.paypal.android.corepayments.CoreConfig 7 | import com.paypal.android.corepayments.RestClient 8 | 9 | internal class CheckoutOrdersAPI( 10 | private val restClient: RestClient, 11 | private val requestFactory: CardRequestFactory = CardRequestFactory(), 12 | private val responseParser: CardResponseParser = CardResponseParser() 13 | ) { 14 | constructor(coreConfig: CoreConfig) : this(RestClient(coreConfig)) 15 | 16 | suspend fun confirmPaymentSource(cardRequest: CardRequest): ConfirmPaymentSourceResult { 17 | val apiRequest = requestFactory.createConfirmPaymentSourceRequest(cardRequest) 18 | val httpResponse = restClient.send(apiRequest) 19 | 20 | val error = responseParser.parseError(httpResponse) 21 | return if (error == null) { 22 | responseParser.parseConfirmPaymentSourceResponse(httpResponse) 23 | } else { 24 | ConfirmPaymentSourceResult.Failure(error) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/api/ConfirmPaymentSourceResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments.api 2 | 3 | import com.paypal.android.cardpayments.model.PaymentSource 4 | import com.paypal.android.cardpayments.model.PurchaseUnit 5 | import com.paypal.android.corepayments.OrderStatus 6 | import com.paypal.android.corepayments.PayPalSDKError 7 | 8 | internal sealed class ConfirmPaymentSourceResult { 9 | 10 | data class Success( 11 | val orderId: String, 12 | val status: OrderStatus? = null, 13 | val payerActionHref: String? = null, 14 | val paymentSource: PaymentSource? = null, 15 | val purchaseUnits: List? = null 16 | ) : ConfirmPaymentSourceResult() 17 | 18 | data class Failure(val error: PayPalSDKError) : ConfirmPaymentSourceResult() 19 | } 20 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/model/Amount.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments.model 2 | 3 | import com.paypal.android.corepayments.PaymentsJSON 4 | 5 | internal data class Amount( 6 | val currencyCode: String?, 7 | val value: String? 8 | ) { 9 | internal constructor(json: PaymentsJSON) : this( 10 | json.optString("currency_code"), 11 | json.optString("value") 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/model/AuthenticationResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments.model 2 | 3 | import com.paypal.android.cardpayments.threedsecure.ThreeDSecureResult 4 | import com.paypal.android.corepayments.PaymentsJSON 5 | import org.json.JSONObject 6 | 7 | internal data class AuthenticationResult( 8 | val liabilityShift: String?, 9 | val threeDSecure: ThreeDSecureResult? = null 10 | ) { 11 | 12 | companion object { 13 | const val KEY_LIABILITY_SHIFT = "liability_shift" 14 | const val KEY_THREE_D_SECURE = "three_d_secure" 15 | } 16 | 17 | internal constructor(json: PaymentsJSON) : this( 18 | json.optString(KEY_LIABILITY_SHIFT), 19 | json.optMapObject(KEY_THREE_D_SECURE) { ThreeDSecureResult(it) } 20 | ) 21 | 22 | fun toJSON(): JSONObject { 23 | return JSONObject() 24 | .putOpt(KEY_LIABILITY_SHIFT, liabilityShift) 25 | .putOpt(KEY_THREE_D_SECURE, threeDSecure?.toJSON()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/model/Payee.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments.model 2 | 3 | import com.paypal.android.corepayments.PaymentsJSON 4 | 5 | internal data class Payee(val emailAddress: String) { 6 | internal constructor(json: PaymentsJSON) : this(json.optString("email_address") ?: "") 7 | } 8 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/model/PaymentSource.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments.model 2 | 3 | import com.paypal.android.corepayments.PaymentsJSON 4 | import org.json.JSONObject 5 | 6 | internal data class PaymentSource( 7 | val lastDigits: String, 8 | val brand: String, 9 | val type: String? = null, 10 | val authenticationResult: AuthenticationResult? = null 11 | ) { 12 | 13 | companion object { 14 | const val KEY_LAST_DIGITS = "last_digits" 15 | const val KEY_BRAND = "brand" 16 | const val KEY_TYPE = "type" 17 | const val KEY_AUTHENTICATION_RESULT = "authentication_result" 18 | } 19 | 20 | internal constructor(json: PaymentsJSON) : this( 21 | json.getString(KEY_LAST_DIGITS), 22 | json.getString(KEY_BRAND), 23 | json.optString(KEY_TYPE), 24 | json.optMapObject(KEY_AUTHENTICATION_RESULT) { AuthenticationResult(it) } 25 | ) 26 | 27 | fun toJSON(): JSONObject { 28 | return JSONObject() 29 | .put(KEY_LAST_DIGITS, lastDigits) 30 | .put(KEY_BRAND, brand) 31 | .putOpt(KEY_TYPE, type) 32 | .putOpt(KEY_AUTHENTICATION_RESULT, authenticationResult?.toJSON()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/model/PurchaseUnit.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments.model 2 | 3 | import com.paypal.android.corepayments.PaymentsJSON 4 | 5 | /** 6 | * A purchase unit for an order. If an order takes multiple purchase units, 7 | * each one must contain a reference id. 8 | */ 9 | internal data class PurchaseUnit( 10 | val referenceId: String?, 11 | val amount: Amount? = null, 12 | val payee: Payee? = null 13 | ) { 14 | internal constructor(json: PaymentsJSON) : this( 15 | json.optString("reference_id"), 16 | json.optMapObject("amount") { Amount(it) }, 17 | json.optMapObject("payee") { Payee(json) } 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/threedsecure/SCA.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments.threedsecure 2 | 3 | // NEXT MAJOR VERSION: Rename SCA to VerificationMethod 4 | enum class SCA { 5 | SCA_ALWAYS, SCA_WHEN_REQUIRED 6 | } 7 | -------------------------------------------------------------------------------- /CardPayments/src/main/java/com/paypal/android/cardpayments/threedsecure/ThreeDSecureResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments.threedsecure 2 | 3 | import com.paypal.android.corepayments.PaymentsJSON 4 | import org.json.JSONObject 5 | 6 | internal data class ThreeDSecureResult( 7 | val enrollmentStatus: String? = null, 8 | val authenticationStatus: String? = null 9 | ) { 10 | 11 | companion object { 12 | const val KEY_ENROLLMENT_STATUS = "enrollment_status" 13 | const val KEY_AUTHENTICATION_STATUS = "authentication_status" 14 | } 15 | 16 | internal constructor(json: PaymentsJSON) : this( 17 | json.optString(KEY_ENROLLMENT_STATUS), 18 | json.optString(KEY_AUTHENTICATION_STATUS) 19 | ) 20 | 21 | fun toJSON(): JSONObject { 22 | return JSONObject() 23 | .putOpt(KEY_ENROLLMENT_STATUS, enrollmentStatus) 24 | .putOpt(KEY_AUTHENTICATION_STATUS, authenticationStatus) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CardPayments/src/main/res/raw/graphql_query_update_setup_token.graphql: -------------------------------------------------------------------------------- 1 | mutation UpdateVaultSetupToken( 2 | $clientId: String! 3 | $vaultSetupToken: String! 4 | $paymentSource: PaymentSource 5 | ) { 6 | updateVaultSetupToken( 7 | clientId: $clientId 8 | vaultSetupToken: $vaultSetupToken 9 | paymentSource: $paymentSource 10 | ) { 11 | id, 12 | status, 13 | links { 14 | rel, 15 | href 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CardPayments/src/test/java/com/paypal/android/cardpayments/ApproveOrderMetadataUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.cardpayments 2 | 3 | import org.json.JSONObject 4 | import org.junit.Test 5 | import org.skyscreamer.jsonassert.JSONAssert 6 | 7 | class ApproveOrderMetadataUnitTest { 8 | 9 | @Test 10 | fun `it should be able to serialize and deserialize itself in JSON`() { 11 | 12 | // language=JSON 13 | val jsonString = """ 14 | { 15 | "order_id": "sample-order-id", 16 | "payment_source": { 17 | "last_digits": "4111", 18 | "brand": "Visa", 19 | "type": "sample-type", 20 | "authentication_result": { 21 | "liability_shift": "YES", 22 | "three_d_secure": { 23 | "enrollment_status": "ENROLLED", 24 | "authentication_status": "AUTHENTICATED" 25 | } 26 | } 27 | } 28 | } 29 | """ 30 | 31 | val originalJSON = JSONObject(jsonString) 32 | val reserializedJSON = ApproveOrderMetadata.fromJSON(originalJSON)?.toJSON() 33 | 34 | JSONAssert.assertEquals(originalJSON, reserializedJSON, true) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /CardPayments/src/test/resources/robolectric.properties: -------------------------------------------------------------------------------- 1 | # TODO: remove this file when Robolectric supports API level 35 (Android 15) 2 | sdk=33 3 | -------------------------------------------------------------------------------- /CorePayments/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /CorePayments/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/CorePayments/consumer-rules.pro -------------------------------------------------------------------------------- /CorePayments/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /CorePayments/src/androidTest/java/com/paypal/android/corepayments/HttpIntegrationTest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import kotlinx.coroutines.ExperimentalCoroutinesApi 5 | import kotlinx.coroutines.test.StandardTestDispatcher 6 | import kotlinx.coroutines.test.runTest 7 | import org.junit.Assert.assertEquals 8 | import org.junit.Test 9 | import org.junit.runner.RunWith 10 | import java.net.URL 11 | 12 | @ExperimentalCoroutinesApi 13 | @RunWith(AndroidJUnit4::class) 14 | class HttpIntegrationTest { 15 | 16 | @Test 17 | fun send_makesAnHttpRequest() = runTest { 18 | val request = HttpRequest(URL("https://www.google.com")) 19 | 20 | val testDispatcher = StandardTestDispatcher(testScheduler) 21 | val sut = Http(testDispatcher) 22 | val result = sut.send(request) 23 | assertEquals(200, result.status) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CorePayments/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/APIRequest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import androidx.annotation.RestrictTo 4 | 5 | /** 6 | * @suppress 7 | */ 8 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 9 | data class APIRequest(val path: String, val method: HttpMethod, val body: String? = null) 10 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/Address.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | 6 | @Parcelize 7 | data class Address( 8 | /** 9 | * The [two-character ISO 3166-1 code], that identifies the country or region. 10 | */ 11 | val countryCode: String, 12 | 13 | /** 14 | * Optional. Line 1 of the Address (eg. number, street, etc) 15 | */ 16 | val streetAddress: String? = null, 17 | 18 | /** 19 | * Optional. Line 2 of the Address (eg. suite, apt #, etc.) 20 | */ 21 | val extendedAddress: String? = null, 22 | 23 | /** 24 | * Optional. City name 25 | */ 26 | val locality: String? = null, 27 | 28 | /** 29 | * Optional. Either a two-letter state code (for the US), or an 30 | * ISO-3166-2 country subdivision code of up to three letters. 31 | */ 32 | val region: String? = null, 33 | 34 | /** 35 | * Optional. Zip code or equivalent is usually required for countries that have them. 36 | * For a list of countries that do not have postal codes please refer to http://en.wikipedia.org/wiki/Postal_code 37 | */ 38 | val postalCode: String? = null 39 | ) : Parcelable 40 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/Base64.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import android.util.Base64 4 | 5 | internal fun String.base64encoded(): String { 6 | val bytes = toByteArray(Charsets.UTF_8) 7 | return Base64.encodeToString(bytes, Base64.NO_WRAP) 8 | } 9 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/BrowserSwitchRequestCodes.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | object BrowserSwitchRequestCodes { 4 | const val CARD_APPROVE_ORDER = 1 5 | const val CARD_VAULT = 2 6 | 7 | const val PAYPAL_CHECKOUT = 3 8 | const val PAYPAL_VAULT = 4 9 | } 10 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/CoreConfig.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | data class CoreConfig @JvmOverloads constructor( 4 | val clientId: String, 5 | val environment: Environment = Environment.SANDBOX, 6 | ) 7 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/CoreCoroutineExceptionHandler.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import androidx.annotation.RestrictTo 4 | import kotlinx.coroutines.CoroutineExceptionHandler 5 | import kotlin.coroutines.AbstractCoroutineContextElement 6 | import kotlin.coroutines.CoroutineContext 7 | 8 | /** 9 | * @suppress 10 | */ 11 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 12 | class CoreCoroutineExceptionHandler(private val handler: (PayPalSDKError) -> Unit) : 13 | AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler { 14 | 15 | override fun handleException(context: CoroutineContext, exception: Throwable) { 16 | val error = when (exception) { 17 | is PayPalSDKError -> exception 18 | else -> { 19 | val message = exception.localizedMessage ?: "Something went wrong" 20 | PayPalSDKError(0, message) 21 | } 22 | } 23 | handler(error) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/Environment.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | enum class Environment(internal val url: String, internal val graphQLEndpoint: String) { 4 | LIVE( 5 | "https://api-m.paypal.com", 6 | "https://www.paypal.com" 7 | ), 8 | SANDBOX( 9 | "https://api-m.sandbox.paypal.com", 10 | "https://www.sandbox.paypal.com" 11 | ), 12 | } 13 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/Http.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import android.util.Log 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import java.io.IOException 8 | import java.lang.IllegalStateException 9 | import java.net.HttpURLConnection 10 | import java.net.UnknownHostException 11 | 12 | internal class Http( 13 | private val dispatcher: CoroutineDispatcher = Dispatchers.IO, 14 | private val httpResponseParser: HttpResponseParser = HttpResponseParser() 15 | ) { 16 | 17 | companion object { 18 | private val TAG = Http::class.qualifiedName 19 | } 20 | 21 | suspend fun send(httpRequest: HttpRequest): HttpResponse = 22 | withContext(dispatcher) { 23 | runCatching { 24 | val url = httpRequest.url 25 | val connection = url.openConnection() as HttpURLConnection 26 | 27 | connection.requestMethod = httpRequest.method.name 28 | 29 | // add headers 30 | for ((key, value) in httpRequest.headers) { 31 | connection.addRequestProperty(key, value) 32 | } 33 | 34 | if (httpRequest.method == HttpMethod.POST) { 35 | try { 36 | connection.doOutput = true 37 | connection.outputStream.write(httpRequest.body?.toByteArray()) 38 | connection.outputStream.flush() 39 | connection.outputStream.close() 40 | } catch (e: IOException) { 41 | Log.d(TAG, "Error closing connection output stream:") 42 | Log.d(TAG, e.stackTrace.toString()) 43 | } 44 | } 45 | 46 | connection.connect() 47 | httpResponseParser.parse(connection) 48 | }.recover { 49 | val status = when (it) { 50 | is UnknownHostException -> HttpResponse.STATUS_UNKNOWN_HOST 51 | is IllegalStateException -> HttpResponse.SERVER_ERROR 52 | else -> HttpResponse.STATUS_UNDETERMINED 53 | } 54 | HttpResponse(status = status, error = it) 55 | }.getOrNull()!! 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/HttpMethod.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import androidx.annotation.RestrictTo 4 | 5 | /** 6 | * @suppress 7 | */ 8 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 9 | enum class HttpMethod { 10 | GET, POST 11 | } 12 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/HttpRequest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import java.net.URL 4 | 5 | internal data class HttpRequest( 6 | val url: URL, 7 | val method: HttpMethod = HttpMethod.GET, 8 | val body: String? = null, 9 | val headers: MutableMap = mutableMapOf(), 10 | ) 11 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/HttpResponse.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import androidx.annotation.RestrictTo 4 | import java.net.HttpURLConnection 5 | 6 | /** 7 | * @suppress 8 | */ 9 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 10 | data class HttpResponse( 11 | val status: Int, 12 | val headers: Map = emptyMap(), 13 | val body: String? = null, 14 | val error: Throwable? = null 15 | ) { 16 | companion object { 17 | const val STATUS_UNDETERMINED = -1 18 | const val STATUS_UNKNOWN_HOST = -2 19 | const val SERVER_ERROR = -3 20 | 21 | val SUCCESSFUL_STATUS_CODES = HttpURLConnection.HTTP_OK..299 22 | } 23 | 24 | var isSuccessful: Boolean = status in SUCCESSFUL_STATUS_CODES 25 | } 26 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/LoadRawResourceResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | sealed class LoadRawResourceResult { 4 | data class Success(val value: String) : LoadRawResourceResult() 5 | data class Failure(val error: PayPalSDKError) : LoadRawResourceResult() 6 | } 7 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/OrderErrorDetail.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import androidx.annotation.RestrictTo 4 | 5 | /** 6 | * @suppress 7 | */ 8 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 9 | data class OrderErrorDetail( 10 | val issue: String, 11 | val description: String 12 | ) { 13 | override fun toString(): String { 14 | return "Issue: $issue.\n" + 15 | "Error description: $description" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/OrderStatus.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import androidx.annotation.RestrictTo 4 | 5 | /** 6 | * The status of an order. 7 | * @suppress 8 | */ 9 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 10 | enum class OrderStatus { 11 | 12 | /** 13 | * The order was created 14 | */ 15 | CREATED, 16 | 17 | /** 18 | * The order was approved with a valid payment source 19 | */ 20 | APPROVED, 21 | 22 | /** 23 | * The order is completed and paid 24 | */ 25 | COMPLETED, 26 | 27 | /** 28 | * The payer is required to take additional action before the order can be approved 29 | */ 30 | PAYER_ACTION_REQUIRED 31 | } 32 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/PayPalSDKError.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import androidx.annotation.RestrictTo 4 | 5 | /** 6 | * Class used to describe PayPal errors when they occur. 7 | */ 8 | class PayPalSDKError @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) constructor( 9 | val code: Int, 10 | val errorDescription: String, 11 | val correlationId: String? = null, 12 | reason: Throwable? = null 13 | ) : Exception("Error: $code - Description: $errorDescription", reason) 14 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/PayPalSDKErrorCode.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import androidx.annotation.RestrictTo 4 | 5 | /** 6 | * @suppress 7 | */ 8 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 9 | enum class PayPalSDKErrorCode { 10 | UNKNOWN, 11 | DATA_PARSING_ERROR, 12 | UNKNOWN_HOST, 13 | NO_RESPONSE_DATA, 14 | INVALID_URL_REQUEST, 15 | SERVER_RESPONSE_ERROR, 16 | CHECKOUT_ERROR, 17 | NATIVE_CHECKOUT_ERROR, 18 | GRAPHQL_JSON_INVALID_ERROR 19 | } 20 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/ResourceLoader.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import android.content.Context 4 | import android.content.res.Resources 5 | import androidx.annotation.RawRes 6 | import androidx.annotation.RestrictTo 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.withContext 9 | import java.io.IOException 10 | 11 | /** 12 | * Convenience class to simplify interaction with Android resource APIs. 13 | * @suppress 14 | */ 15 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 16 | class ResourceLoader { 17 | 18 | /** 19 | * Load an Android raw resource as a String using a background IO thread. 20 | * 21 | * @param context Android context 22 | * @param resId ID of the resource that will be loaded 23 | */ 24 | suspend fun loadRawResource(context: Context, @RawRes resId: Int): LoadRawResourceResult = 25 | withContext(Dispatchers.IO) { 26 | try { 27 | val resInputStream = context.resources.openRawResource(resId) 28 | val resAsBytes = ByteArray(resInputStream.available()) 29 | resInputStream.read(resAsBytes) 30 | resInputStream.close() 31 | LoadRawResourceResult.Success(String(resAsBytes)) 32 | } catch (e: Resources.NotFoundException) { 33 | val errorDescription = "Resource with id $resId not found." 34 | val resourceNotFoundError = PayPalSDKError(0, errorDescription, reason = e) 35 | LoadRawResourceResult.Failure(resourceNotFoundError) 36 | } catch (e: IOException) { 37 | val errorDescription = "Error loading resource with id $resId." 38 | val ioError = PayPalSDKError(0, errorDescription, reason = e) 39 | LoadRawResourceResult.Failure(ioError) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/RestClient.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import androidx.annotation.RestrictTo 4 | import java.net.URL 5 | import java.util.Locale 6 | 7 | /** 8 | * @suppress 9 | */ 10 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 11 | class RestClient internal constructor( 12 | private val configuration: CoreConfig, 13 | private val http: Http = Http(), 14 | private val language: String = Locale.getDefault().language 15 | ) { 16 | 17 | constructor(configuration: CoreConfig) : this(configuration, Http()) 18 | 19 | suspend fun send(apiRequest: APIRequest): HttpResponse { 20 | val httpRequest = createHttpRequestFromAPIRequest(apiRequest, configuration) 21 | return http.send(httpRequest) 22 | } 23 | 24 | private fun createHttpRequestFromAPIRequest( 25 | apiRequest: APIRequest, 26 | configuration: CoreConfig, 27 | ): HttpRequest { 28 | val path = apiRequest.path 29 | val baseUrl = configuration.environment.url 30 | 31 | val url = URL("$baseUrl/$path") 32 | val method = apiRequest.method 33 | val body = apiRequest.body 34 | 35 | // default headers 36 | val headers: MutableMap = mutableMapOf( 37 | "Accept-Encoding" to "gzip", 38 | "Accept-Language" to language 39 | ) 40 | 41 | // use client-id-only Basic authentication 42 | val credentials = "${configuration.clientId}:" 43 | headers["Authorization"] = "Basic ${credentials.base64encoded()}" 44 | 45 | if (method == HttpMethod.POST) { 46 | headers["Content-Type"] = "application/json" 47 | } 48 | return HttpRequest(url, method, body, headers) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/analytics/AnalyticsEventData.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments.analytics 2 | 3 | // Ref: https://blog.klipse.tech/databook/2022/06/22/separate-code-from-data.html 4 | internal data class AnalyticsEventData( 5 | val environment: String, 6 | val eventName: String, 7 | val timestamp: Long, 8 | val orderId: String?, 9 | val buttonType: String? = null 10 | ) 11 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/analytics/DeviceData.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments.analytics 2 | 3 | internal data class DeviceData( 4 | val appId: String, 5 | val appName: String, 6 | val clientOS: String, 7 | val clientSDKVersion: String, 8 | val merchantAppVersion: String?, 9 | val deviceManufacturer: String, 10 | val deviceModel: String, 11 | val isSimulator: Boolean, 12 | ) 13 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/graphql/GraphQLError.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments.graphql 2 | 3 | import androidx.annotation.RestrictTo 4 | 5 | /** 6 | * @suppress 7 | */ 8 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 9 | data class GraphQLError( 10 | val message: String, 11 | val extensions: List? = null 12 | ) 13 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/graphql/GraphQLExtension.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments.graphql 2 | 3 | import androidx.annotation.RestrictTo 4 | 5 | /** 6 | * @suppress 7 | */ 8 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 9 | data class GraphQLExtension( 10 | val correlationId: String, 11 | val code: String? = null 12 | ) 13 | -------------------------------------------------------------------------------- /CorePayments/src/main/java/com/paypal/android/corepayments/graphql/GraphQLResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments.graphql 2 | 3 | import androidx.annotation.RestrictTo 4 | import com.paypal.android.corepayments.PayPalSDKError 5 | import org.json.JSONObject 6 | 7 | /** 8 | * @suppress 9 | */ 10 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 11 | sealed class GraphQLResult { 12 | 13 | data class Success( 14 | val data: JSONObject? = null, 15 | val extensions: List? = null, 16 | val errors: List? = null, 17 | val correlationId: String? = null 18 | ) : GraphQLResult() 19 | 20 | data class Failure(val error: PayPalSDKError) : GraphQLResult() 21 | } 22 | -------------------------------------------------------------------------------- /CorePayments/src/test/java/com/paypal/android/corepayments/CoreConfigUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | class CoreConfigUnitTest { 7 | 8 | @Test 9 | fun `it should default to SANDBOX environment`() { 10 | val sut = CoreConfig("fake-client-id") 11 | assertEquals(Environment.SANDBOX, sut.environment) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CorePayments/src/test/java/com/paypal/android/corepayments/EnvironmentUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | class EnvironmentUnitTest { 7 | 8 | @Test 9 | fun `it should return the correct url for the LIVE environment`() { 10 | assertEquals("https://api-m.paypal.com", Environment.LIVE.url) 11 | } 12 | 13 | @Test 14 | fun `it should return the correct url for the SANDBOX environment`() { 15 | assertEquals("https://api-m.sandbox.paypal.com", Environment.SANDBOX.url) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CorePayments/src/test/java/com/paypal/android/corepayments/TestUtils.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.corepayments 2 | 3 | import kotlinx.coroutines.runBlocking 4 | import org.junit.Assert.assertThrows 5 | 6 | inline fun assertThrows( 7 | noinline executable: suspend () -> Unit 8 | ) = assertThrows(T::class.java) { 9 | runBlocking { 10 | executable() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CorePayments/src/test/resources/robolectric.properties: -------------------------------------------------------------------------------- 1 | # TODO: remove this file when Robolectric supports API level 35 (Android 15) 2 | sdk=33 3 | -------------------------------------------------------------------------------- /Demo/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /Demo/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /Demo/src/androidTest/java/com/paypal/android/CardTest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android 2 | 3 | import androidx.compose.ui.test.ExperimentalTestApi 4 | import androidx.compose.ui.test.hasText 5 | import androidx.compose.ui.test.junit4.createAndroidComposeRule 6 | import androidx.compose.ui.test.onNodeWithText 7 | import androidx.compose.ui.test.performClick 8 | import androidx.test.ext.junit.runners.AndroidJUnit4 9 | import org.junit.Rule 10 | import org.junit.Test 11 | import org.junit.runner.RunWith 12 | 13 | @OptIn(ExperimentalTestApi::class) 14 | @RunWith(AndroidJUnit4::class) 15 | class CardTest { 16 | 17 | @get:Rule 18 | val composeTestRule = createAndroidComposeRule() 19 | 20 | @Test 21 | fun approveOrder() { 22 | val waitTimeoutMs = 15_000L 23 | 24 | composeTestRule.onNodeWithText("Approve Order").performClick() 25 | 26 | composeTestRule.waitUntilExactlyOneExists(hasText("New Visa"), waitTimeoutMs) 27 | composeTestRule.onNodeWithText("New Visa").performClick() 28 | 29 | composeTestRule.waitUntilExactlyOneExists(hasText("Create Order"), waitTimeoutMs) 30 | composeTestRule.onNodeWithText("Create Order").performClick() 31 | 32 | // wait for approve order form 33 | composeTestRule.waitUntilExactlyOneExists(hasText("APPROVE ORDER"), waitTimeoutMs) 34 | 35 | // skip 3DS for this test 36 | composeTestRule.onNodeWithText("SCA").performClick() 37 | composeTestRule.onNodeWithText("WHEN REQUIRED").performClick() 38 | 39 | // approve the order 40 | composeTestRule.onNodeWithText("APPROVE ORDER").performClick() 41 | 42 | composeTestRule.waitUntilExactlyOneExists(hasText("Complete Order"), waitTimeoutMs) 43 | composeTestRule.onNodeWithText("AUTHORIZE ORDER").performClick() 44 | 45 | composeTestRule.waitUntilExactlyOneExists(hasText("Order Complete"), waitTimeoutMs) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Demo/src/androidTestShared/java/com/paypal/android/testutils/AppDriver.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.testutils 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import androidx.test.core.app.ApplicationProvider 7 | import androidx.test.platform.app.InstrumentationRegistry 8 | import androidx.test.uiautomator.By 9 | import androidx.test.uiautomator.UiDevice 10 | import androidx.test.uiautomator.UiObject 11 | import androidx.test.uiautomator.UiSelector 12 | import androidx.test.uiautomator.Until 13 | import org.junit.Assert 14 | 15 | // Ref: https://github.com/android/testing-samples 16 | class AppDriver(private val appPackage: String) { 17 | 18 | companion object { 19 | const val LAUNCH_TIMEOUT = 10000L 20 | } 21 | 22 | private val device: UiDevice = 23 | UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) 24 | 25 | fun launchAppFromHomeScreen() { 26 | device.pressHome() 27 | 28 | val launcherPackage = getLauncherPackageName() 29 | Assert.assertNotNull(launcherPackage) 30 | device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT) 31 | 32 | val context: Context = ApplicationProvider.getApplicationContext() 33 | val intent = context.packageManager.getLaunchIntentForPackage(appPackage)!! 34 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 35 | 36 | context.startActivity(intent) 37 | device.wait(Until.hasObject(By.pkg(appPackage).depth(0)), LAUNCH_TIMEOUT) 38 | } 39 | 40 | fun findResById(id: String): UiObject { 41 | return device.findObject(UiSelector().resourceId(id)) 42 | } 43 | 44 | fun findText(text: String): UiObject { 45 | return device.findObject(UiSelector().text(text)) 46 | } 47 | 48 | fun waitForText(text: String, timeout: Long? = null) { 49 | device.wait(Until.hasObject(By.text(text)), timeout ?: LAUNCH_TIMEOUT) 50 | } 51 | 52 | private fun getLauncherPackageName(): String { 53 | val intent = Intent(Intent.ACTION_MAIN) 54 | intent.addCategory(Intent.CATEGORY_HOME) 55 | 56 | val context: Context = ApplicationProvider.getApplicationContext() 57 | val resolveInfo = context.packageManager.resolveActivity( 58 | intent, 59 | PackageManager.MATCH_DEFAULT_ONLY 60 | ) 61 | return resolveInfo!!.activityInfo.packageName 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Demo/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.foundation.ExperimentalFoundationApi 7 | import androidx.compose.material3.ExperimentalMaterial3Api 8 | import androidx.compose.ui.ExperimentalComposeUiApi 9 | import com.paypal.android.ui.DemoApp 10 | import dagger.hilt.android.AndroidEntryPoint 11 | 12 | @AndroidEntryPoint 13 | class MainActivity : ComponentActivity() { 14 | 15 | @ExperimentalComposeUiApi 16 | @ExperimentalMaterial3Api 17 | @ExperimentalFoundationApi 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | setContent { 21 | DemoApp() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class MainApplication : Application() 8 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/api/model/CardPaymentToken.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.api.model 2 | 3 | data class CardPaymentToken( 4 | val id: String, 5 | val customerId: String, 6 | val cardLast4: String, 7 | val cardBrand: String 8 | ) 9 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/api/model/CardSetupToken.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.api.model 2 | 3 | data class CardSetupToken( 4 | val id: String, 5 | val customerId: String, 6 | val status: String, 7 | val verificationStatus: String? = null, 8 | ) 9 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/api/model/ClientId.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.api.model 2 | 3 | data class ClientId( 4 | val value: String 5 | ) 6 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/api/model/Order.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.api.model 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | 6 | @Parcelize 7 | data class Order( 8 | val id: String? = null, 9 | val intent: String? = null, 10 | val status: String? = null, 11 | val cardLast4: String? = null, 12 | val cardBrand: String? = null, 13 | val vaultId: String? = null, 14 | val customerId: String? = null 15 | ) : Parcelable 16 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/api/model/OrderIntent.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.api.model 2 | 3 | /** 4 | * The intent to either capture payment immediately or authorize a payment for an order after order creation 5 | */ 6 | enum class OrderIntent { 7 | /** 8 | * The merchant intends to capture payment immediately after the customer makes a payment. 9 | */ 10 | CAPTURE, 11 | /** 12 | * The merchant intends to authorize a payment and place funds on hold after the customer makes 13 | * a payment. Authorized payments are best captured within three days of authorization but are 14 | * available to capture for up to 29 days. After the three-day honor period, the original authorized 15 | * payment expires and you must re-authorize the payment. You must make a separate request to 16 | * capture payments on demand. This intent is not supported when you have more than one 17 | * `purchase_unit` within your order. 18 | */ 19 | AUTHORIZE 20 | } 21 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/api/model/PayPalPaymentToken.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.api.model 2 | 3 | data class PayPalPaymentToken( 4 | val id: String, 5 | val customerId: String, 6 | ) 7 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/api/model/PayPalSetupToken.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.api.model 2 | 3 | data class PayPalSetupToken( 4 | val id: String, 5 | val customerId: String, 6 | val status: String 7 | ) 8 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/api/services/MerchantIntegration.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.api.services 2 | 3 | enum class MerchantIntegration(val baseUrl: String) { 4 | DEFAULT("https://sdk-sample-merchant-server.herokuapp.com/"), 5 | DIRECT("https://sdk-sample-merchant-server.herokuapp.com/direct/"), 6 | CONNECTED_PATH("https://sdk-sample-merchant-server.herokuapp.com/connected_path/"), 7 | MANAGED_PATH("https://sdk-sample-merchant-server.herokuapp.com/managed_path/"), 8 | } 9 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/api/services/SDKSampleServerException.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.api.services 2 | 3 | class SDKSampleServerException(message: String?, cause: Throwable?) : Exception(message, cause) 4 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/api/services/SDKSampleServerResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.api.services 2 | 3 | import com.paypal.android.uishared.state.ActionState 4 | 5 | sealed class SDKSampleServerResult { 6 | data class Success(val value: S) : SDKSampleServerResult() 7 | data class Failure(val value: E) : SDKSampleServerResult() 8 | 9 | fun mapToActionState(): ActionState = when (this) { 10 | is Success -> ActionState.Success(value) 11 | is Failure -> ActionState.Failure(value) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.di 2 | 3 | import com.paypal.android.api.services.SDKSampleServerAPI 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.components.SingletonComponent 8 | 9 | @Module 10 | @InstallIn(SingletonComponent::class) 11 | object NetworkModule { 12 | 13 | @Provides 14 | fun provideSDKSampleServerAPI(): SDKSampleServerAPI = SDKSampleServerAPI() 15 | } 16 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/models/OrderRequest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.models 2 | 3 | import com.paypal.android.api.model.OrderIntent 4 | 5 | data class OrderRequest( 6 | val intent: OrderIntent, 7 | val shouldVault: Boolean = false 8 | ) 9 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/DemoAppDestinations.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui 2 | 3 | object DemoAppDestinations { 4 | const val CARD_APPROVE_ORDER = "card_approve_order" 5 | const val FEATURES_ROUTE = "features" 6 | const val CARD_VAULT = "card_vault" 7 | const val PAYPAL_WEB = "paypal_web" 8 | const val PAYPAL_WEB_VAULT = "paypal_web_vault" 9 | const val PAYPAL_BUTTONS = "paypal_buttons" 10 | const val PAYPAL_STATIC_BUTTONS = "paypal_static_buttons" 11 | const val SELECT_TEST_CARD = "select_test_card" 12 | 13 | fun titleForDestination(destination: String?): String = when (destination) { 14 | CARD_APPROVE_ORDER -> "Card Approve Order" 15 | FEATURES_ROUTE -> "Features" 16 | CARD_VAULT -> "Vault Card" 17 | PAYPAL_WEB -> "PayPal Web" 18 | PAYPAL_BUTTONS -> "PayPal Buttons" 19 | PAYPAL_STATIC_BUTTONS -> "PayPal Static Buttons" 20 | SELECT_TEST_CARD -> "Select a Test Card" 21 | PAYPAL_WEB_VAULT -> "PayPal Web Vault" 22 | else -> "Demo" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/approveorder/ApproveOrderUiState.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.approveorder 2 | 3 | import androidx.compose.runtime.Immutable 4 | import com.paypal.android.api.model.Order 5 | import com.paypal.android.api.model.OrderIntent 6 | import com.paypal.android.cardpayments.threedsecure.SCA 7 | import com.paypal.android.uishared.enums.StoreInVaultOption 8 | import com.paypal.android.uishared.state.ActionState 9 | 10 | @Immutable 11 | data class ApproveOrderUiState( 12 | val createOrderState: ActionState = ActionState.Idle, 13 | val approveOrderState: ActionState = ActionState.Idle, 14 | val completeOrderState: ActionState = ActionState.Idle, 15 | val scaOption: SCA = SCA.SCA_ALWAYS, 16 | val cardNumber: String = "", 17 | val cardExpirationDate: String = "", 18 | val cardSecurityCode: String = "", 19 | val intentOption: OrderIntent = OrderIntent.AUTHORIZE, 20 | val shouldVault: StoreInVaultOption = StoreInVaultOption.NO, 21 | ) { 22 | val isCreateOrderSuccessful: Boolean 23 | get() = createOrderState is ActionState.Success 24 | 25 | val isApproveOrderSuccessful: Boolean 26 | get() = approveOrderState is ActionState.Success 27 | } 28 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/approveorder/CardFormatter.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.approveorder 2 | 3 | object CardFormatter { 4 | 5 | /** 6 | * Formats a card number string. 7 | * Example: "4000000000001091" -> "4000 0000 0000 1091" 8 | * 9 | * @param newCardNumber - card number to format 10 | * @param previousCardNumber - previous value that was entered into the card field. This value 11 | * is needed to handle the deletion of characters. 12 | */ 13 | fun formatCardNumber(newCardNumber: String, previousCardNumber: String = ""): String { 14 | if (newCardNumber.length < previousCardNumber.length) return newCardNumber 15 | var cardString = newCardNumber.replace(" ", "") 16 | 17 | val cardType = getCardType(cardString) 18 | return if (previousCardNumber.length == cardType.maxCardLength + cardType.cardNumberIndices.size) { 19 | previousCardNumber 20 | } else { 21 | for (index in cardType.cardNumberIndices) { 22 | if (index <= cardString.length) { 23 | cardString = cardString.insertChar(index, ' ') 24 | } 25 | } 26 | cardString 27 | } 28 | } 29 | 30 | private fun String.insertChar(index: Int, char: Char): String { 31 | return substring(0, index) + char + substring(index, length) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/approveorder/CardType.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.approveorder 2 | 3 | import java.lang.Integer.max 4 | 5 | @Suppress("MagicNumber") 6 | enum class CardType( 7 | val cardNumberIndices: List, 8 | val digitGroupings: List, 9 | val maxCardLength: Int, 10 | val allDigitGroupingsExceptLast: List = digitGroupings.slice( 11 | 0 until max( 12 | 0, 13 | digitGroupings.size - 1 14 | ) 15 | ) 16 | ) { 17 | // Ref: https://stackoverflow.com/a/65726499 18 | 19 | AMERICAN_EXPRESS( 20 | cardNumberIndices = listOf(4, 11), 21 | digitGroupings = listOf(4, 6, 5), 22 | maxCardLength = 15 23 | ), 24 | VISA( 25 | cardNumberIndices = listOf(4, 9, 14), 26 | digitGroupings = listOf(4, 4, 4, 4), 27 | maxCardLength = 16 28 | ), 29 | UNKNOWN_CARD( 30 | cardNumberIndices = listOf(4, 9, 14), 31 | digitGroupings = emptyList(), 32 | maxCardLength = 16 33 | ) 34 | } 35 | 36 | fun getCardType(cardNumber: String): CardType { 37 | return when { 38 | cardNumber.startsWith("34") || cardNumber.startsWith("37") -> { 39 | CardType.AMERICAN_EXPRESS 40 | } 41 | 42 | cardNumber.startsWith("4") -> CardType.VISA 43 | else -> CardType.UNKNOWN_CARD 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/approveorder/DateVisualTransformation.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.approveorder 2 | 3 | import androidx.compose.ui.text.AnnotatedString 4 | import androidx.compose.ui.text.input.OffsetMapping 5 | import androidx.compose.ui.text.input.TransformedText 6 | import androidx.compose.ui.text.input.VisualTransformation 7 | 8 | class DateVisualTransformation : VisualTransformation { 9 | 10 | override fun filter(text: AnnotatedString): TransformedText { 11 | val dateString = DateString(text.text) 12 | return TransformedText( 13 | AnnotatedString(formatDate(dateString)), 14 | offsetMapping = DateOffsetMapping(dateString) 15 | ) 16 | } 17 | 18 | private fun formatDate(dateString: DateString): String { 19 | return dateString.formatted 20 | } 21 | 22 | class DateOffsetMapping(private val dateString: DateString) : OffsetMapping { 23 | 24 | override fun originalToTransformed(offset: Int): Int { 25 | return dateString.mapRawOffsetToFormatted(offset) 26 | } 27 | 28 | override fun transformedToOriginal(offset: Int): Int { 29 | return dateString.mapFormattedOffsetToRawOffset(offset) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/approveorder/OrderInfo.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.approveorder 2 | 3 | data class OrderInfo( 4 | val orderId: String, 5 | val status: String?, 6 | val didAttemptThreeDSecureAuthentication: Boolean 7 | ) 8 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/approveorder/SetupTokenInfo.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.approveorder 2 | 3 | data class SetupTokenInfo( 4 | val setupTokenId: String, 5 | val status: String? = null, 6 | val didAttemptThreeDSecureAuthentication: Boolean = false 7 | ) 8 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/features/Feature.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.features 2 | 3 | import androidx.annotation.StringRes 4 | import com.paypal.android.R 5 | import com.paypal.android.ui.DemoAppDestinations 6 | 7 | enum class Feature(@StringRes val stringRes: Int, val routeName: String) { 8 | CARD_APPROVE_ORDER(R.string.feature_approve_order, DemoAppDestinations.CARD_APPROVE_ORDER), 9 | CARD_VAULT(R.string.feature_vault, DemoAppDestinations.CARD_VAULT), 10 | PAYPAL_WEB(R.string.feature_paypal_web, DemoAppDestinations.PAYPAL_WEB), 11 | PAYPAL_WEB_VAULT(R.string.feature_paypal_web_vault, DemoAppDestinations.PAYPAL_WEB_VAULT), 12 | PAYPAL_BUTTONS(R.string.feature_paypal_buttons, DemoAppDestinations.PAYPAL_BUTTONS), 13 | PAYPAL_STATIC_BUTTONS( 14 | R.string.feature_paypal_static_buttons, 15 | DemoAppDestinations.PAYPAL_STATIC_BUTTONS 16 | ), 17 | } 18 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalbuttons/ButtonFundingType.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalbuttons 2 | 3 | enum class ButtonFundingType { 4 | PAYPAL, 5 | PAY_LATER, 6 | PAYPAL_CREDIT; 7 | } 8 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalbuttons/PayPalButtonColorOptionList.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalbuttons 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.paypal.android.R 6 | import com.paypal.android.paymentbuttons.PayPalButtonColor 7 | import com.paypal.android.uishared.components.OptionList 8 | 9 | @Composable 10 | fun PayPalButtonColorOptionList( 11 | selectedOption: PayPalButtonColor, 12 | onSelection: (PayPalButtonColor) -> Unit 13 | ) { 14 | OptionList( 15 | title = stringResource(id = R.string.pay_pal_button_color), 16 | options = PayPalButtonColor.values().map { it.name }, 17 | selectedOption = selectedOption.name, 18 | onSelectedOptionChange = { option -> 19 | onSelection(PayPalButtonColor.valueOf(option)) 20 | } 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalbuttons/PayPalButtonFundingTypeOptionList.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalbuttons 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.paypal.android.R 6 | import com.paypal.android.uishared.components.OptionList 7 | 8 | @Composable 9 | fun PayPalButtonFundingTypeOptionList( 10 | selectedOption: ButtonFundingType, 11 | onSelection: (ButtonFundingType) -> Unit 12 | ) { 13 | OptionList( 14 | title = stringResource(id = R.string.pay_pal_button_type), 15 | options = ButtonFundingType.values().map { it.name }, 16 | selectedOption = selectedOption.name, 17 | onSelectedOptionChange = { option -> 18 | onSelection(ButtonFundingType.valueOf(option)) 19 | } 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalbuttons/PayPalButtonLabelOptionList.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalbuttons 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.paypal.android.R 6 | import com.paypal.android.paymentbuttons.PayPalButtonLabel 7 | import com.paypal.android.uishared.components.OptionList 8 | 9 | @Composable 10 | fun PayPalButtonLabelOptionList( 11 | selectedOption: PayPalButtonLabel, 12 | onSelection: (PayPalButtonLabel) -> Unit 13 | ) { 14 | OptionList( 15 | title = stringResource(id = R.string.pay_pal_button_label), 16 | options = PayPalButtonLabel.values().map { it.name }, 17 | selectedOption = selectedOption.name, 18 | onSelectedOptionChange = { option -> 19 | onSelection(PayPalButtonLabel.valueOf(option)) 20 | } 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalbuttons/PayPalButtonsUiState.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalbuttons 2 | 3 | import com.paypal.android.paymentbuttons.PayPalButtonColor 4 | import com.paypal.android.paymentbuttons.PayPalButtonLabel 5 | import com.paypal.android.paymentbuttons.PayPalCreditButtonColor 6 | import com.paypal.android.paymentbuttons.PaymentButtonShape 7 | import com.paypal.android.paymentbuttons.PaymentButtonSize 8 | 9 | data class PayPalButtonsUiState( 10 | val fundingType: ButtonFundingType = ButtonFundingType.PAYPAL, 11 | val payPalCreditButtonColor: PayPalCreditButtonColor = PayPalCreditButtonColor.DARK_BLUE, 12 | val payPalButtonColor: PayPalButtonColor = PayPalButtonColor.GOLD, 13 | val payPalButtonLabel: PayPalButtonLabel = PayPalButtonLabel.PAYPAL, 14 | val paymentButtonShape: PaymentButtonShape = PaymentButtonShape.ROUNDED, 15 | val paymentButtonSize: PaymentButtonSize = PaymentButtonSize.SMALL, 16 | val customCornerRadius: Int? = null 17 | ) 18 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalbuttons/PayPalButtonsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalbuttons 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.paypal.android.paymentbuttons.PayPalButtonColor 5 | import com.paypal.android.paymentbuttons.PayPalButtonLabel 6 | import com.paypal.android.paymentbuttons.PayPalCreditButtonColor 7 | import com.paypal.android.paymentbuttons.PaymentButtonShape 8 | import com.paypal.android.paymentbuttons.PaymentButtonSize 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.flow.asStateFlow 11 | import kotlinx.coroutines.flow.update 12 | 13 | class PayPalButtonsViewModel : ViewModel() { 14 | private val _uiState = MutableStateFlow(PayPalButtonsUiState()) 15 | val uiState = _uiState.asStateFlow() 16 | 17 | var selectedFundingType: ButtonFundingType 18 | get() = _uiState.value.fundingType 19 | set(value) { 20 | _uiState.update { it.copy(fundingType = value) } 21 | } 22 | 23 | var payPalButtonColor: PayPalButtonColor 24 | get() = _uiState.value.payPalButtonColor 25 | set(value) { 26 | _uiState.update { it.copy(payPalButtonColor = value) } 27 | } 28 | 29 | var payPalCreditButtonColor: PayPalCreditButtonColor 30 | get() = _uiState.value.payPalCreditButtonColor 31 | set(value) { 32 | _uiState.update { it.copy(payPalCreditButtonColor = value) } 33 | } 34 | 35 | var payPalButtonLabel: PayPalButtonLabel 36 | get() = _uiState.value.payPalButtonLabel 37 | set(value) { 38 | _uiState.update { it.copy(payPalButtonLabel = value) } 39 | } 40 | 41 | var customCornerRadius: Int? 42 | get() = _uiState.value.customCornerRadius 43 | set(value) { 44 | _uiState.update { it.copy(customCornerRadius = value) } 45 | } 46 | var paymentButtonShape: PaymentButtonShape 47 | get() = _uiState.value.paymentButtonShape 48 | set(value) { 49 | _uiState.update { 50 | it.copy( 51 | paymentButtonShape = value, 52 | customCornerRadius = null 53 | ) 54 | } 55 | } 56 | 57 | var paymentButtonSize: PaymentButtonSize 58 | get() = _uiState.value.paymentButtonSize 59 | set(value) { 60 | _uiState.update { it.copy(paymentButtonSize = value) } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalbuttons/PayPalCreditButtonColorOptionList.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalbuttons 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.paypal.android.R 6 | import com.paypal.android.paymentbuttons.PayPalCreditButtonColor 7 | import com.paypal.android.uishared.components.OptionList 8 | 9 | @Composable 10 | fun PayPalCreditButtonColorOptionList( 11 | selectedOption: PayPalCreditButtonColor, 12 | onSelection: (PayPalCreditButtonColor) -> Unit 13 | ) { 14 | OptionList( 15 | title = stringResource(id = R.string.pay_pal_button_color), 16 | options = PayPalCreditButtonColor.values().map { it.name }, 17 | selectedOption = selectedOption.name, 18 | onSelectedOptionChange = { option -> 19 | onSelection(PayPalCreditButtonColor.valueOf(option)) 20 | } 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalbuttons/PaymentButtonShapeOptionList.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalbuttons 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.paypal.android.R 6 | import com.paypal.android.paymentbuttons.PaymentButtonShape 7 | import com.paypal.android.uishared.components.OptionList 8 | 9 | @Composable 10 | fun PaymentButtonShapeOptionList( 11 | selectedOption: PaymentButtonShape, 12 | onSelection: (PaymentButtonShape) -> Unit 13 | ) { 14 | OptionList( 15 | title = stringResource(id = R.string.pay_pal_button_shape), 16 | options = PaymentButtonShape.values().map { it.name }, 17 | selectedOption = selectedOption.name, 18 | onSelectedOptionChange = { option -> 19 | onSelection(PaymentButtonShape.valueOf(option)) 20 | } 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalbuttons/PaymentButtonSizeOptionList.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalbuttons 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.paypal.android.R 6 | import com.paypal.android.paymentbuttons.PaymentButtonSize 7 | import com.paypal.android.uishared.components.OptionList 8 | 9 | @Composable 10 | fun PaymentButtonSizeOptionList( 11 | selectedOption: PaymentButtonSize, 12 | onSelection: (PaymentButtonSize) -> Unit 13 | ) { 14 | OptionList( 15 | title = stringResource(id = R.string.pay_pal_button_size), 16 | options = PaymentButtonSize.values().map { it.name }, 17 | selectedOption = selectedOption.name, 18 | onSelectedOptionChange = { option -> 19 | onSelection(PaymentButtonSize.valueOf(option)) 20 | } 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalstaticbuttons/PayPalStaticButtonsView.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalstaticbuttons 2 | 3 | import android.annotation.SuppressLint 4 | import android.view.LayoutInflater 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.viewinterop.AndroidView 9 | import com.paypal.android.R 10 | 11 | @SuppressLint("InflateParams") 12 | @Composable 13 | fun PayPalStaticButtonsView() { 14 | AndroidView( 15 | factory = { context -> 16 | val view = LayoutInflater.from(context) 17 | .inflate(R.layout.pay_later_button_test_layout, null, false) 18 | 19 | view 20 | }, 21 | modifier = Modifier.fillMaxSize() 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebCheckoutResultView.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalweb 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import com.paypal.android.uishared.components.PropertyView 8 | import com.paypal.android.utils.UIConstants 9 | 10 | @Composable 11 | fun PayPalWebCheckoutResultView(orderId: String?, payerId: String?) { 12 | Column( 13 | verticalArrangement = UIConstants.spacingMedium, 14 | modifier = Modifier.padding(UIConstants.paddingMedium) 15 | ) { 16 | PropertyView(name = "Order ID", value = orderId) 17 | PropertyView(name = "Payer ID", value = payerId) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebUiState.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalweb 2 | 3 | import com.paypal.android.api.model.Order 4 | import com.paypal.android.api.model.OrderIntent 5 | import com.paypal.android.paypalwebpayments.PayPalWebCheckoutFinishStartResult 6 | import com.paypal.android.paypalwebpayments.PayPalWebCheckoutFundingSource 7 | import com.paypal.android.uishared.state.ActionState 8 | 9 | data class PayPalWebUiState( 10 | val intentOption: OrderIntent = OrderIntent.AUTHORIZE, 11 | val createOrderState: ActionState = ActionState.Idle, 12 | val payPalWebCheckoutState: ActionState = ActionState.Idle, 13 | val completeOrderState: ActionState = ActionState.Idle, 14 | val fundingSource: PayPalWebCheckoutFundingSource = PayPalWebCheckoutFundingSource.PAYPAL 15 | ) { 16 | val isCreateOrderSuccessful: Boolean 17 | get() = createOrderState is ActionState.Success 18 | 19 | val isPayPalWebCheckoutSuccessful: Boolean 20 | get() = payPalWebCheckoutState is ActionState.Success 21 | } 22 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalweb/StartPayPalWebCheckoutForm.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalweb 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.res.stringResource 6 | import com.paypal.android.R 7 | import com.paypal.android.paypalwebpayments.PayPalWebCheckoutFundingSource 8 | import com.paypal.android.uishared.components.EnumOptionList 9 | import com.paypal.android.utils.UIConstants 10 | 11 | @Composable 12 | fun StartPayPalWebCheckoutForm( 13 | fundingSource: PayPalWebCheckoutFundingSource, 14 | onFundingSourceChange: (PayPalWebCheckoutFundingSource) -> Unit, 15 | ) { 16 | Column( 17 | verticalArrangement = UIConstants.spacingMedium 18 | ) { 19 | EnumOptionList( 20 | title = stringResource(id = R.string.pay_pal_funding_source_title), 21 | stringArrayResId = R.array.pay_pal_funding_source_options, 22 | onSelectedOptionChange = onFundingSourceChange, 23 | selectedOption = fundingSource 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/paypalwebvault/PayPalWebVaultUiState.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.paypalwebvault 2 | 3 | import com.paypal.android.api.model.PayPalPaymentToken 4 | import com.paypal.android.api.model.PayPalSetupToken 5 | import com.paypal.android.paypalwebpayments.PayPalWebCheckoutFinishVaultResult 6 | import com.paypal.android.uishared.state.ActionState 7 | 8 | data class PayPalWebVaultUiState( 9 | val createSetupTokenState: ActionState = ActionState.Idle, 10 | val vaultPayPalState: ActionState = ActionState.Idle, 11 | val createPaymentTokenState: ActionState = ActionState.Idle, 12 | ) { 13 | val isCreateSetupTokenSuccessful: Boolean 14 | get() = createSetupTokenState is ActionState.Success 15 | 16 | val isVaultPayPalSuccessful: Boolean 17 | get() = vaultPayPalState is ActionState.Success 18 | } 19 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/selectcard/SelectCardViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.selectcard 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.paypal.android.models.TestCard 5 | import java.util.Calendar 6 | 7 | class SelectCardViewModel : ViewModel() { 8 | companion object { 9 | // 2 years into the future of the current year 10 | val validExpirationYear = "${Calendar.getInstance().get(Calendar.YEAR) + 2}" 11 | } 12 | 13 | val verifiedTestCards = listOf( 14 | TestCard.VISA_VAULT_WITH_PURCHASE_NO_3DS 15 | ) 16 | 17 | val nonThreeDSCards = listOf( 18 | TestCard.VISA_NO_3DS_1, 19 | TestCard.VISA_NO_3DS_2 20 | ) 21 | 22 | val threeDSCards = listOf( 23 | TestCard.VISA_3DS_SUCCESSFUL_AUTH, 24 | TestCard.VISA_3DS_FAILED_SIGNATURE, 25 | TestCard.VISA_3DS_FAILED_AUTHENTICATION, 26 | TestCard.VISA_3DS_PASSIVE_AUTHENTICATION, 27 | TestCard.VISA_3DS_TRANSACTION_TIMEOUT, 28 | TestCard.VISA_3DS_NOT_ENROLLED, 29 | TestCard.VISA_3DS_AUTH_SYSTEM_UNAVAILABLE, 30 | TestCard.VISA_3DS_AUTH_ERROR, 31 | TestCard.VISA_3DS_AUTH_UNAVAILABLE, 32 | TestCard.VISA_3DS_MERCHANT_BYPASSED_AUTH, 33 | TestCard.VISA_3DS_MERCHANT_INACTIVE, 34 | TestCard.VISA_3DS_CMPI_LOOKUP_ERROR 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/ui/vaultcard/VaultCardUiState.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.ui.vaultcard 2 | 3 | import com.paypal.android.api.model.CardPaymentToken 4 | import com.paypal.android.api.model.CardSetupToken 5 | import com.paypal.android.ui.approveorder.SetupTokenInfo 6 | import com.paypal.android.cardpayments.threedsecure.SCA 7 | import com.paypal.android.uishared.state.ActionState 8 | 9 | data class VaultCardUiState( 10 | val createSetupTokenState: ActionState = ActionState.Idle, 11 | val updateSetupTokenState: ActionState = ActionState.Idle, 12 | val createPaymentTokenState: ActionState = ActionState.Idle, 13 | val cardNumber: String = "", 14 | val cardExpirationDate: String = "", 15 | val cardSecurityCode: String = "", 16 | val scaOption: SCA = SCA.SCA_WHEN_REQUIRED, 17 | ) { 18 | val isCreateSetupTokenSuccessful: Boolean 19 | get() = createSetupTokenState is ActionState.Success 20 | 21 | val isVaultCardSuccessful: Boolean 22 | get() = updateSetupTokenState is ActionState.Success 23 | } 24 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/BooleanOptionList.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.layout.padding 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.Surface 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.tooling.preview.Preview 9 | import com.paypal.android.utils.UIConstants 10 | 11 | @Composable 12 | fun BooleanOptionList( 13 | title: String, 14 | selectedOption: Boolean, 15 | onSelectedOptionChange: (Boolean) -> Unit, 16 | modifier: Modifier = Modifier, 17 | ) { 18 | val options = listOf("NO", "YES") 19 | val selectedOptionString = if (selectedOption) "YES" else "NO" 20 | OptionList( 21 | title = title, 22 | options = options, 23 | selectedOption = selectedOptionString, 24 | onSelectedOptionChange = { value -> onSelectedOptionChange(value == "YES") }, 25 | modifier = modifier 26 | ) 27 | } 28 | 29 | @Preview 30 | @Composable 31 | fun BooleanOptionListPreview() { 32 | MaterialTheme { 33 | Surface { 34 | BooleanOptionList( 35 | title = "Fake Title", 36 | onSelectedOptionChange = {}, 37 | selectedOption = true, 38 | modifier = Modifier.padding(UIConstants.paddingSmall) 39 | ) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/CardPaymentTokenView.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import com.paypal.android.api.model.CardPaymentToken 8 | import com.paypal.android.utils.UIConstants 9 | 10 | @Composable 11 | fun CardPaymentTokenView(paymentToken: CardPaymentToken) { 12 | Column( 13 | verticalArrangement = UIConstants.spacingMedium, 14 | modifier = Modifier.padding(UIConstants.paddingMedium) 15 | ) { 16 | PropertyView(name = "ID", value = paymentToken.id) 17 | PropertyView(name = "Customer ID", value = paymentToken.customerId) 18 | PropertyView(name = "Card Brand", value = paymentToken.cardBrand) 19 | PropertyView(name = "Card Last 4", value = paymentToken.cardLast4) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/CardResultView.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.Surface 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.tooling.preview.Preview 11 | import com.paypal.android.ui.approveorder.OrderInfo 12 | import com.paypal.android.utils.UIConstants 13 | 14 | @Composable 15 | fun CardResultView(result: OrderInfo) { 16 | Column( 17 | verticalArrangement = UIConstants.spacingMedium, 18 | modifier = Modifier 19 | .fillMaxWidth() 20 | .padding(UIConstants.paddingMedium) 21 | ) { 22 | PropertyView(name = "Order ID", value = result.orderId) 23 | PropertyView(name = "Order Status", value = result.status) 24 | val didAttemptText = if (result.didAttemptThreeDSecureAuthentication) "YES" else "NO" 25 | PropertyView(name = "Did Attempt 3DS Authentication", value = didAttemptText) 26 | } 27 | } 28 | 29 | @Preview 30 | @Composable 31 | fun CardResultViewWith3DSAuth() { 32 | MaterialTheme { 33 | Surface(modifier = Modifier.fillMaxWidth()) { 34 | val result = OrderInfo( 35 | orderId = "fake-order-id", 36 | status = "fake-status", 37 | didAttemptThreeDSecureAuthentication = true 38 | ) 39 | CardResultView(result) 40 | } 41 | } 42 | } 43 | 44 | @Preview 45 | @Composable 46 | fun CardResultViewWithout3DSAuth() { 47 | MaterialTheme { 48 | Surface(modifier = Modifier.fillMaxWidth()) { 49 | val result = OrderInfo( 50 | orderId = "fake-order-id", 51 | status = "fake-status", 52 | didAttemptThreeDSecureAuthentication = false 53 | ) 54 | CardResultView(result) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/CardSetupTokenView.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import com.paypal.android.api.model.CardSetupToken 8 | import com.paypal.android.utils.UIConstants 9 | 10 | @Composable 11 | fun CardSetupTokenView(setupToken: CardSetupToken) { 12 | Column( 13 | verticalArrangement = UIConstants.spacingMedium, 14 | modifier = Modifier.padding(UIConstants.paddingMedium) 15 | ) { 16 | PropertyView(name = "ID", value = setupToken.id) 17 | PropertyView(name = "Customer ID", value = setupToken.customerId) 18 | PropertyView(name = "Status", value = setupToken.status) 19 | setupToken.verificationStatus?.let { verificationStatus -> 20 | PropertyView(name = "Verification Status", value = verificationStatus) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/CardVaultResultView.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import com.paypal.android.ui.approveorder.SetupTokenInfo 8 | import com.paypal.android.utils.UIConstants 9 | 10 | @Composable 11 | fun CardVaultResultView(result: SetupTokenInfo) { 12 | Column( 13 | verticalArrangement = UIConstants.spacingMedium, 14 | modifier = Modifier.padding(UIConstants.paddingMedium) 15 | ) { 16 | PropertyView(name = "Setup Token ID", value = result.setupTokenId) 17 | PropertyView(name = "Status", value = result.status) 18 | val didAttemptText = if (result.didAttemptThreeDSecureAuthentication) "YES" else "NO" 19 | PropertyView(name = "Did Attempt 3DS Authentication", value = didAttemptText) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/CreateOrderForm.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.Surface 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.res.stringResource 10 | import androidx.compose.ui.tooling.preview.Preview 11 | import com.paypal.android.R 12 | import com.paypal.android.api.model.OrderIntent 13 | import com.paypal.android.utils.UIConstants 14 | 15 | @Composable 16 | fun CreateOrderForm( 17 | orderIntent: OrderIntent = OrderIntent.AUTHORIZE, 18 | onOrderIntentChange: (OrderIntent) -> Unit = {}, 19 | ) { 20 | Column( 21 | verticalArrangement = UIConstants.spacingMedium 22 | ) { 23 | EnumOptionList( 24 | title = stringResource(id = R.string.intent_title), 25 | stringArrayResId = R.array.intent_options, 26 | onSelectedOptionChange = { onOrderIntentChange(it) }, 27 | selectedOption = orderIntent 28 | ) 29 | } 30 | } 31 | 32 | @Preview 33 | @Composable 34 | fun CreateOrderFormPreview() { 35 | MaterialTheme { 36 | Surface(modifier = Modifier.fillMaxSize()) { 37 | CreateOrderForm() 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/CreateOrderWithVaultOptionForm.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.Surface 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.res.stringResource 10 | import androidx.compose.ui.tooling.preview.Preview 11 | import com.paypal.android.R 12 | import com.paypal.android.api.model.OrderIntent 13 | import com.paypal.android.uishared.enums.StoreInVaultOption 14 | import com.paypal.android.utils.UIConstants 15 | 16 | @Composable 17 | fun CreateOrderWithVaultOptionForm( 18 | orderIntent: OrderIntent = OrderIntent.AUTHORIZE, 19 | shouldVault: StoreInVaultOption = StoreInVaultOption.NO, 20 | onShouldVaultChanged: (StoreInVaultOption) -> Unit = {}, 21 | onIntentOptionChanged: (OrderIntent) -> Unit = {}, 22 | ) { 23 | Column( 24 | verticalArrangement = UIConstants.spacingMedium 25 | ) { 26 | EnumOptionList( 27 | title = stringResource(id = R.string.intent_title), 28 | stringArrayResId = R.array.intent_options, 29 | onSelectedOptionChange = { onIntentOptionChanged(it) }, 30 | selectedOption = orderIntent 31 | ) 32 | EnumOptionList( 33 | title = stringResource(id = R.string.store_in_vault), 34 | stringArrayResId = R.array.store_in_vault_options, 35 | onSelectedOptionChange = { onShouldVaultChanged(it) }, 36 | selectedOption = shouldVault 37 | ) 38 | } 39 | } 40 | 41 | @Preview 42 | @Composable 43 | fun CreateOrderWithVaultOptionFormPreview() { 44 | MaterialTheme { 45 | Surface(modifier = Modifier.fillMaxSize()) { 46 | CreateOrderWithVaultOptionForm() 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/DemoAppTopBar.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.material.icons.Icons 5 | import androidx.compose.material.icons.filled.ArrowBack 6 | import androidx.compose.material3.CenterAlignedTopAppBar 7 | import androidx.compose.material3.ExperimentalMaterial3Api 8 | import androidx.compose.material3.Icon 9 | import androidx.compose.material3.IconButton 10 | import androidx.compose.material3.MaterialTheme 11 | import androidx.compose.material3.Text 12 | import androidx.compose.material3.TopAppBarDefaults 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.ui.Modifier 15 | 16 | @ExperimentalMaterial3Api 17 | @Composable 18 | fun DemoAppTopBar( 19 | title: String, 20 | shouldDisplayBackButton: Boolean, 21 | onBackButtonClick: () -> Unit 22 | ) { 23 | CenterAlignedTopAppBar( 24 | title = { 25 | Text(text = title) 26 | }, 27 | colors = TopAppBarDefaults.topAppBarColors( 28 | containerColor = MaterialTheme.colorScheme.surface, 29 | titleContentColor = MaterialTheme.colorScheme.onSurface 30 | ), 31 | navigationIcon = { 32 | // Ref: https://stackoverflow.com/a/70409412 33 | if (shouldDisplayBackButton) { 34 | IconButton(onClick = onBackButtonClick) { 35 | Icon( 36 | imageVector = Icons.Filled.ArrowBack, 37 | contentDescription = "Back" 38 | ) 39 | } 40 | } 41 | }, 42 | modifier = Modifier 43 | .background(MaterialTheme.colorScheme.surface) 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/ErrorView.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.Surface 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.tooling.preview.Preview 11 | import com.paypal.android.corepayments.PayPalSDKError 12 | import com.paypal.android.utils.UIConstants 13 | 14 | @Composable 15 | fun ErrorView(error: Exception) { 16 | Column( 17 | verticalArrangement = UIConstants.spacingMedium, 18 | modifier = Modifier.padding(UIConstants.paddingMedium) 19 | ) { 20 | if (error is PayPalSDKError) { 21 | PropertyView(name = "Error Code", value = "${error.code}") 22 | PropertyView(name = "Error Description", value = error.errorDescription) 23 | PropertyView(name = "Correlation ID", value = error.correlationId) 24 | } else { 25 | PropertyView(name = "Message", value = error.message) 26 | } 27 | } 28 | } 29 | 30 | @Preview 31 | @Composable 32 | fun ErrorViewActionColumnPreview() { 33 | MaterialTheme { 34 | Surface(modifier = Modifier.fillMaxSize()) { 35 | ErrorView(error = java.lang.Exception("Fake Exception")) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/InfoColumn.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.material3.Card 9 | import androidx.compose.material3.MaterialTheme 10 | import androidx.compose.material3.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.text.style.TextAlign 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import com.paypal.android.utils.UIConstants 16 | 17 | @Composable 18 | fun InfoColumn( 19 | title: String, 20 | modifier: Modifier = Modifier, 21 | content: @Composable () -> Unit = {}, 22 | ) { 23 | Card( 24 | modifier = modifier 25 | ) { 26 | Row( 27 | modifier = Modifier 28 | .background(MaterialTheme.colorScheme.inverseSurface), 29 | ) { 30 | Text( 31 | text = title, 32 | color = MaterialTheme.colorScheme.inverseOnSurface, 33 | style = MaterialTheme.typography.bodyLarge, 34 | textAlign = TextAlign.Center, 35 | modifier = Modifier 36 | .padding(UIConstants.paddingMedium) 37 | .fillMaxWidth() 38 | ) 39 | } 40 | Column { 41 | content() 42 | } 43 | } 44 | } 45 | 46 | @Preview 47 | @Composable 48 | fun InfoColumnPreview() { 49 | MaterialTheme { 50 | Column { 51 | InfoColumn( 52 | title = "Sample Title" 53 | ) { 54 | Text( 55 | text = "Sample Text", 56 | modifier = Modifier.padding(UIConstants.paddingLarge) 57 | ) 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/IntSlider.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.material3.Slider 4 | import androidx.compose.material3.SliderColors 5 | import androidx.compose.runtime.Composable 6 | 7 | @Composable 8 | fun IntSlider( 9 | value: Int, 10 | onValueChange: (Int) -> Unit, 11 | valueRange: ClosedRange, 12 | colors: SliderColors, 13 | steps: Int, 14 | ) { 15 | Slider( 16 | value = value.toFloat(), 17 | valueRange = valueRange.start.toFloat()..valueRange.endInclusive.toFloat(), 18 | steps = steps, 19 | colors = colors, 20 | onValueChange = { onValueChange(it.toInt()) } 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/OrderView.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.Surface 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.tooling.preview.Preview 12 | import com.paypal.android.api.model.Order 13 | import com.paypal.android.utils.UIConstants 14 | 15 | @Composable 16 | fun OrderView(order: Order, title: String? = null) { 17 | Column( 18 | modifier = Modifier 19 | .fillMaxWidth() 20 | .padding(UIConstants.paddingMedium) 21 | ) { 22 | title?.let { text -> 23 | Text( 24 | text = text, 25 | style = MaterialTheme.typography.titleLarge, 26 | modifier = Modifier.padding(vertical = UIConstants.paddingMedium) 27 | ) 28 | } 29 | Column( 30 | verticalArrangement = UIConstants.spacingMedium, 31 | modifier = Modifier 32 | .fillMaxWidth() 33 | ) { 34 | PropertyView(name = "ID", value = order.id) 35 | order.intent?.let { PropertyView(name = "Intent", value = it) } 36 | order.status?.let { PropertyView(name = "Status", value = it) } 37 | order.cardLast4?.let { PropertyView(name = "Card Last 4", value = order.cardLast4) } 38 | order.cardBrand?.let { PropertyView(name = "Card Brand", value = order.cardBrand) } 39 | order.vaultId?.let { 40 | PropertyView(name = "Vault Id / Payment Token", value = order.vaultId) 41 | } 42 | order.customerId?.let { 43 | PropertyView(name = "Customer Vault Id", value = order.customerId) 44 | } 45 | } 46 | } 47 | } 48 | 49 | @Preview 50 | @Composable 51 | fun OrderPreview() { 52 | MaterialTheme { 53 | Surface(modifier = Modifier.fillMaxWidth()) { 54 | OrderView(Order(), "Sample Title") 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/PayPalPaymentTokenView.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import com.paypal.android.api.model.PayPalPaymentToken 8 | import com.paypal.android.utils.UIConstants 9 | 10 | @Composable 11 | fun PayPalPaymentTokenView(paymentToken: PayPalPaymentToken) { 12 | Column( 13 | verticalArrangement = UIConstants.spacingMedium, 14 | modifier = Modifier.padding(UIConstants.paddingMedium) 15 | ) { 16 | PropertyView(name = "ID", value = paymentToken.id) 17 | PropertyView(name = "Customer ID", value = paymentToken.customerId) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/PayPalSetupTokenView.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import com.paypal.android.api.model.PayPalSetupToken 8 | import com.paypal.android.utils.UIConstants 9 | 10 | @Composable 11 | fun PayPalSetupTokenView(setupToken: PayPalSetupToken) { 12 | Column( 13 | verticalArrangement = UIConstants.spacingMedium, 14 | modifier = Modifier.padding(UIConstants.paddingMedium) 15 | ) { 16 | PropertyView(name = "ID", value = setupToken.id) 17 | PropertyView(name = "Customer ID", value = setupToken.customerId) 18 | PropertyView(name = "Status", value = setupToken.status) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/PropertyView.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.Text 6 | import androidx.compose.runtime.Composable 7 | import com.paypal.android.utils.UIConstants 8 | 9 | @Composable 10 | fun PropertyView(name: String, value: String?) { 11 | Column( 12 | verticalArrangement = UIConstants.spacingExtraSmall 13 | ) { 14 | Text( 15 | text = name, 16 | style = MaterialTheme.typography.titleMedium, 17 | ) 18 | Text(text = value ?: "UNSET") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/components/UriView.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.components 2 | 3 | import android.net.Uri 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import com.paypal.android.utils.UIConstants 12 | 13 | @Composable 14 | fun UriView(uri: Uri) { 15 | Row( 16 | horizontalArrangement = Arrangement.spacedBy(UIConstants.paddingSmall), 17 | modifier = Modifier 18 | .padding(top = UIConstants.paddingSmall) 19 | ) { 20 | Text("Scheme:") 21 | Text(uri.scheme ?: "NOT SET") 22 | } 23 | Row( 24 | horizontalArrangement = Arrangement.spacedBy(UIConstants.paddingSmall), 25 | modifier = Modifier 26 | .padding(top = UIConstants.paddingSmall) 27 | ) { 28 | Text("Host:") 29 | Text(uri.host ?: "NOT SET") 30 | } 31 | Row( 32 | horizontalArrangement = Arrangement.spacedBy(UIConstants.paddingSmall), 33 | modifier = Modifier 34 | .padding(top = UIConstants.paddingSmall) 35 | ) { 36 | Text("Path:") 37 | Text(uri.path ?: "NOT SET") 38 | } 39 | val queryParameterNames = uri.queryParameterNames ?: emptySet() 40 | if (queryParameterNames.isNotEmpty()) { 41 | Text( 42 | text = "Params:", 43 | modifier = Modifier 44 | .padding(top = UIConstants.paddingSmall) 45 | ) 46 | Column( 47 | modifier = Modifier.padding(UIConstants.paddingSmall) 48 | ) { 49 | for (paramName in queryParameterNames) { 50 | Row( 51 | horizontalArrangement = Arrangement.spacedBy(UIConstants.paddingSmall) 52 | ) { 53 | Text("$paramName:") 54 | Text(uri.getQueryParameter(paramName) ?: "PRESENT BUT NOT SET") 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/effects/NavDestinationChangeDisposableEffect.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.effects 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.DisposableEffect 5 | import androidx.navigation.NavController 6 | 7 | @Composable 8 | fun NavDestinationChangeDisposableEffect( 9 | navController: NavController, 10 | onDestinationChange: (controller: NavController) -> Unit 11 | ) { 12 | // Ref: https://stackoverflow.com/a/68700967 13 | DisposableEffect(navController) { 14 | val listener = NavController.OnDestinationChangedListener { controller, _, _ -> 15 | onDestinationChange(controller) 16 | } 17 | navController.addOnDestinationChangedListener(listener) 18 | onDispose { 19 | navController.removeOnDestinationChangedListener(listener) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/enums/StoreInVaultOption.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.enums 2 | 3 | enum class StoreInVaultOption { 4 | ON_SUCCESS, NO 5 | } 6 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/state/ActionState.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.state 2 | 3 | sealed class ActionState { 4 | object Idle : ActionState() 5 | object Loading : ActionState() 6 | data class Success(val value: S) : ActionState() 7 | data class Failure(val value: E) : ActionState() 8 | 9 | val isComplete: Boolean 10 | get() = (this is Success) || (this is Failure) 11 | } 12 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/uishared/state/CompletedActionState.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.uishared.state 2 | 3 | sealed class CompletedActionState { 4 | data class Success(val value: S) : CompletedActionState() 5 | data class Failure(val value: E) : CompletedActionState() 6 | } 7 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/usecase/CompleteOrderUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.usecase 2 | 3 | import com.paypal.android.api.model.Order 4 | import com.paypal.android.api.model.OrderIntent 5 | import com.paypal.android.api.services.SDKSampleServerAPI 6 | import com.paypal.android.api.services.SDKSampleServerResult 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.withContext 9 | import javax.inject.Inject 10 | 11 | class CompleteOrderUseCase @Inject constructor( 12 | private val sdkSampleServerAPI: SDKSampleServerAPI 13 | ) { 14 | 15 | suspend operator fun invoke( 16 | orderId: String, 17 | intent: OrderIntent, 18 | clientMetadataId: String 19 | ): SDKSampleServerResult = withContext(Dispatchers.IO) { 20 | when (intent) { 21 | OrderIntent.CAPTURE -> 22 | sdkSampleServerAPI.captureOrder(orderId, clientMetadataId) 23 | 24 | OrderIntent.AUTHORIZE -> 25 | sdkSampleServerAPI.authorizeOrder(orderId, clientMetadataId) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/usecase/CreateCardPaymentTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.usecase 2 | 3 | import com.google.gson.JsonObject 4 | import com.google.gson.JsonParser 5 | import com.paypal.android.api.model.CardPaymentToken 6 | import com.paypal.android.api.model.CardSetupToken 7 | import com.paypal.android.api.services.SDKSampleServerAPI 8 | import com.paypal.android.api.services.SDKSampleServerResult 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.withContext 11 | import javax.inject.Inject 12 | 13 | class CreateCardPaymentTokenUseCase @Inject constructor( 14 | private val sdkSampleServerAPI: SDKSampleServerAPI 15 | ) { 16 | 17 | suspend operator fun invoke(setupToken: CardSetupToken): SDKSampleServerResult = 18 | withContext(Dispatchers.IO) { 19 | // language=JSON 20 | val request = """ 21 | { 22 | "payment_source": { 23 | "token": { 24 | "id": "${setupToken.id}", 25 | "type": "SETUP_TOKEN" 26 | } 27 | } 28 | } 29 | """ 30 | 31 | val requestJson = JsonParser.parseString(request) as JsonObject 32 | sdkSampleServerAPI.createPaymentToken(requestJson) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/usecase/CreateCardSetupTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.usecase 2 | 3 | import com.google.gson.JsonObject 4 | import com.google.gson.JsonParser 5 | import com.paypal.android.api.model.CardSetupToken 6 | import com.paypal.android.api.services.SDKSampleServerAPI 7 | import com.paypal.android.api.services.SDKSampleServerResult 8 | import com.paypal.android.cardpayments.threedsecure.SCA 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.withContext 11 | import javax.inject.Inject 12 | 13 | class CreateCardSetupTokenUseCase @Inject constructor( 14 | private val sdkSampleServerAPI: SDKSampleServerAPI 15 | ) { 16 | 17 | suspend operator fun invoke(sca: SCA): SDKSampleServerResult = 18 | withContext(Dispatchers.IO) { 19 | // create a payment token with an empty card attribute; the merchant app will 20 | // provide the card's details through the SDK 21 | // language=JSON 22 | val request = """ 23 | { 24 | "payment_source": { 25 | "card": { 26 | "verification_method": "${sca.name}", 27 | "experience_context": { 28 | "return_url": "com.paypal.android.demo://vault/success", 29 | "cancel_url": "com.paypal.android.demo://vault/cancel" 30 | } 31 | } 32 | } 33 | } 34 | """ 35 | val jsonOrder = JsonParser.parseString(request) as JsonObject 36 | sdkSampleServerAPI.createSetupToken(jsonOrder) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/usecase/CreateOrderUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.usecase 2 | 3 | import com.paypal.android.api.model.Order 4 | import com.paypal.android.api.services.SDKSampleServerAPI 5 | import com.paypal.android.api.services.SDKSampleServerResult 6 | import com.paypal.android.models.OrderRequest 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.withContext 9 | import org.json.JSONArray 10 | import org.json.JSONObject 11 | import javax.inject.Inject 12 | 13 | class CreateOrderUseCase @Inject constructor( 14 | private val sdkSampleServerAPI: SDKSampleServerAPI 15 | ) { 16 | 17 | suspend operator fun invoke(request: OrderRequest): SDKSampleServerResult = 18 | withContext(Dispatchers.IO) { 19 | val amountJSON = JSONObject() 20 | .put("currency_code", "USD") 21 | .put("value", "10.99") 22 | 23 | val purchaseUnitJSON = JSONObject() 24 | .put("amount", amountJSON) 25 | 26 | val orderRequest = JSONObject() 27 | .put("intent", request.intent) 28 | .put("purchase_units", JSONArray().put(purchaseUnitJSON)) 29 | 30 | if (request.shouldVault) { 31 | val vaultJSON = JSONObject() 32 | .put("store_in_vault", "ON_SUCCESS") 33 | 34 | val cardAttributesJSON = JSONObject() 35 | .put("vault", vaultJSON) 36 | 37 | val cardJSON = JSONObject() 38 | .put("attributes", cardAttributesJSON) 39 | 40 | val paymentSourceJSON = JSONObject() 41 | .put("card", cardJSON) 42 | 43 | orderRequest.put("payment_source", paymentSourceJSON) 44 | } 45 | sdkSampleServerAPI.createOrder(orderRequest) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/usecase/CreatePayPalPaymentTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.usecase 2 | 3 | import com.google.gson.JsonObject 4 | import com.google.gson.JsonParser 5 | import com.paypal.android.api.model.PayPalPaymentToken 6 | import com.paypal.android.api.model.PayPalSetupToken 7 | import com.paypal.android.api.services.SDKSampleServerAPI 8 | import com.paypal.android.api.services.SDKSampleServerResult 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.withContext 11 | import javax.inject.Inject 12 | 13 | class CreatePayPalPaymentTokenUseCase @Inject constructor( 14 | private val sdkSampleServerAPI: SDKSampleServerAPI 15 | ) { 16 | suspend operator fun invoke(setupToken: PayPalSetupToken): SDKSampleServerResult = 17 | withContext(Dispatchers.IO) { 18 | // language=JSON 19 | val request = """ 20 | { 21 | "payment_source": { 22 | "token": { 23 | "id": "${setupToken.id}", 24 | "type": "SETUP_TOKEN" 25 | } 26 | } 27 | } 28 | """ 29 | 30 | val requestJson = JsonParser.parseString(request) as JsonObject 31 | sdkSampleServerAPI.createPayPalPaymentToken(requestJson) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/usecase/CreatePayPalSetupTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.usecase 2 | 3 | import com.google.gson.JsonObject 4 | import com.google.gson.JsonParser 5 | import com.paypal.android.api.model.PayPalSetupToken 6 | import com.paypal.android.api.services.SDKSampleServerAPI 7 | import com.paypal.android.api.services.SDKSampleServerResult 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.withContext 10 | import javax.inject.Inject 11 | 12 | class CreatePayPalSetupTokenUseCase @Inject constructor( 13 | private val sdkSampleServerAPI: SDKSampleServerAPI 14 | ) { 15 | 16 | suspend operator fun invoke(): SDKSampleServerResult = 17 | withContext(Dispatchers.IO) { 18 | // language=JSON 19 | val request = """ 20 | { 21 | "payment_source": { 22 | "paypal": { 23 | "usage_type": "MERCHANT", 24 | "experience_context": { 25 | "vault_instruction": "ON_PAYER_APPROVAL", 26 | "return_url": "com.paypal.android.demo://vault/success", 27 | "cancel_url": "com.paypal.android.demo://vault/cancel" 28 | } 29 | } 30 | } 31 | } 32 | """ 33 | 34 | // Ref: https://stackoverflow.com/a/19610814 35 | val body = request.replace("\\/", "/") 36 | 37 | val jsonOrder = JsonParser.parseString(body) as JsonObject 38 | sdkSampleServerAPI.createPayPalSetupToken(jsonOrder) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/usecase/GetClientIdUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.usecase 2 | 3 | import com.paypal.android.api.services.SDKSampleServerAPI 4 | import com.paypal.android.api.services.SDKSampleServerResult 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import javax.inject.Inject 8 | 9 | class GetClientIdUseCase @Inject constructor( 10 | private val sdkSampleServerAPI: SDKSampleServerAPI 11 | ) { 12 | suspend operator fun invoke(): SDKSampleServerResult = withContext(Dispatchers.IO) { 13 | sdkSampleServerAPI.fetchClientId() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/usecase/GetSetupTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.usecase 2 | 3 | import com.paypal.android.api.model.CardSetupToken 4 | import com.paypal.android.api.services.SDKSampleServerAPI 5 | import com.paypal.android.api.services.SDKSampleServerResult 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.withContext 8 | import javax.inject.Inject 9 | 10 | class GetSetupTokenUseCase @Inject constructor( 11 | private val sdkSampleServerAPI: SDKSampleServerAPI 12 | ) { 13 | 14 | suspend operator fun invoke(setupTokenId: String): SDKSampleServerResult = 15 | withContext(Dispatchers.IO) { 16 | sdkSampleServerAPI.getSetupToken(setupTokenId) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/utils/ContextExt.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.utils 2 | 3 | import android.content.Context 4 | import android.content.ContextWrapper 5 | import androidx.activity.ComponentActivity 6 | 7 | // Ref: https://stackoverflow.com/a/68423182 8 | fun Context.getActivityOrNull(): ComponentActivity? = when (this) { 9 | is ComponentActivity -> this 10 | is ContextWrapper -> baseContext.getActivityOrNull() 11 | else -> null 12 | } 13 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/utils/OnLifecycleOwnerResumeEffect.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.utils 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.LaunchedEffect 5 | import androidx.compose.runtime.collectAsState 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.ui.platform.LocalLifecycleOwner 8 | import androidx.lifecycle.Lifecycle 9 | 10 | @Composable 11 | fun OnLifecycleOwnerResumeEffect(callback: () -> Unit) { 12 | // Ref: https://stackoverflow.com/a/66549433 13 | val lifecycleOwner = LocalLifecycleOwner.current 14 | val lifecycleState by lifecycleOwner.lifecycle.currentStateFlow.collectAsState() 15 | LaunchedEffect(lifecycleState) { 16 | if (lifecycleState == Lifecycle.State.RESUMED) { 17 | callback() 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/utils/OnNewIntentEffect.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.utils 2 | 3 | import android.content.Intent 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.DisposableEffect 6 | import androidx.compose.ui.platform.LocalContext 7 | import androidx.core.util.Consumer 8 | 9 | @Composable 10 | fun OnNewIntentEffect(callback: (newIntent: Intent) -> Unit) { 11 | val context = LocalContext.current 12 | // pass "Unit" to register listener only once 13 | DisposableEffect(Unit) { 14 | val listener = Consumer { newIntent -> 15 | callback(newIntent) 16 | } 17 | context.getActivityOrNull()?.addOnNewIntentListener(listener) 18 | onDispose { 19 | context.getActivityOrNull()?.removeOnNewIntentListener(listener) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Demo/src/main/java/com/paypal/android/utils/UIConstants.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.utils 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.ui.unit.dp 5 | 6 | object UIConstants { 7 | private const val slideInOffsetPercentY = 0.05 8 | fun getSlideInStartOffsetY(fullHeightOfComposable: Int) = 9 | (fullHeightOfComposable * slideInOffsetPercentY).toInt() 10 | 11 | // Ref: https://support.google.com/accessibility/android/answer/7101858 12 | val minimumTouchSize = 48.dp 13 | val stepNumberBackgroundSize = 40.dp 14 | val buttonCornerRadius = 16.dp 15 | val chevronSize = 16.dp 16 | val progressIndicatorSize = 32.dp 17 | 18 | val paddingExtraSmall = 4.dp 19 | val paddingSmall = 8.dp 20 | val paddingMedium = 16.dp 21 | val paddingLarge = 24.dp 22 | 23 | val spacingExtraSmall = Arrangement.spacedBy(paddingExtraSmall) 24 | val spacingSmall = Arrangement.spacedBy(paddingSmall) 25 | val spacingMedium = Arrangement.spacedBy(paddingMedium) 26 | val spacingLarge = Arrangement.spacedBy(paddingLarge) 27 | } 28 | -------------------------------------------------------------------------------- /Demo/src/main/play/default-language.txt: -------------------------------------------------------------------------------- 1 | en-US 2 | -------------------------------------------------------------------------------- /Demo/src/main/play/listings/en-US/title.txt: -------------------------------------------------------------------------------- 1 | PayPal SDK 2 | -------------------------------------------------------------------------------- /Demo/src/main/play/release-notes/en-US/internal.txt: -------------------------------------------------------------------------------- 1 | Test Release 2 | -------------------------------------------------------------------------------- /Demo/src/main/res/drawable/chevron.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /Demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /Demo/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /Demo/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF000000 4 | #FFFFFFFF 5 | #FF00457C 6 | #FF0079C1 7 | #FF36454f 8 | -------------------------------------------------------------------------------- /Demo/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidSDK 3 | 4 | 5 | Approve Order 6 | Vault 7 | PayPal Web 8 | PayPal Web Vault 9 | PayPal Buttons 10 | PayPal Buttons - XML 11 | 12 | 13 | CARD NUMBER 14 | EXP. DATE 15 | SEC. CODE 16 | 17 | SCA 18 | 19 | SCA_ALWAYS 20 | SCA_WHEN_REQUIRED 21 | 22 | 23 | Button Preview 24 | Button Options 25 | Button Type 26 | Button Color 27 | Button Label 28 | Button Shape 29 | Button Size 30 | 31 | INTENT 32 | 33 | AUTHORIZE 34 | CAPTURE 35 | 36 | 37 | STORE IN VAULT 38 | 39 | NO 40 | ON_SUCCESS 41 | 42 | 43 | FUNDING SOURCE 44 | 45 | PAYPAL_CREDIT 46 | PAY_LATER 47 | PAYPAL 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Demo/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /Demo/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Demo/src/test/java/com/paypal/android/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android 2 | 3 | import junit.framework.TestCase.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /FraudProtection/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /FraudProtection/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias libs.plugins.android.library 3 | alias libs.plugins.kotlinAndroid 4 | } 5 | 6 | android { 7 | namespace 'com.paypal.android.fraudprotection' 8 | java { 9 | toolchain { 10 | languageVersion = modules.kotlinToolchainLanguageVersion 11 | } 12 | } 13 | 14 | defaultConfig { 15 | compileSdk modules.androidCompileSdk 16 | minSdkVersion modules.androidMinSdkVersion 17 | targetSdkVersion modules.androidTargetVersion 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | consumerProguardFiles "consumer-rules.pro" 20 | } 21 | 22 | buildTypes { 23 | release { 24 | version = rootProject.version 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | 30 | compileOptions { 31 | sourceCompatibility modules.sourceCompatibility 32 | targetCompatibility modules.targetCompatibility 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = modules.kotlinJvmTarget 37 | } 38 | 39 | testOptions { 40 | unitTests.returnDefaultValues = true 41 | } 42 | namespace 'com.paypal.android.fraudprotection' 43 | } 44 | 45 | dependencies { 46 | implementation files('libs/android-magnessdk-5.5.1.jar') 47 | 48 | api project(':CorePayments') 49 | implementation libs.kotlin.stdLib 50 | implementation libs.androidx.coreKtx 51 | implementation libs.androidx.appcompat 52 | implementation libs.androidx.preferenceKtx 53 | 54 | testImplementation libs.junit 55 | testImplementation libs.mockk 56 | testImplementation libs.jsonAssert 57 | testImplementation libs.robolectric 58 | 59 | androidTestImplementation libs.androidx.test.junit 60 | androidTestImplementation libs.androidx.test.espresso 61 | } 62 | 63 | 64 | project.ext.name = "fraud-protection" 65 | project.ext.version = rootProject.version 66 | project.ext.pom_name = "PayPal SDK - Fraud Protection" 67 | project.ext.pom_desc = "Library for PayPal's fraud protection" 68 | 69 | apply from: rootProject.file("gradle/gradle-publish.gradle") -------------------------------------------------------------------------------- /FraudProtection/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/FraudProtection/consumer-rules.pro -------------------------------------------------------------------------------- /FraudProtection/libs/android-magnessdk-5.5.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/FraudProtection/libs/android-magnessdk-5.5.1.jar -------------------------------------------------------------------------------- /FraudProtection/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /FraudProtection/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /FraudProtection/src/main/java/com/paypal/android/fraudprotection/CoreConfigMagnesExt.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.fraudprotection 2 | 3 | import com.paypal.android.corepayments.CoreConfig 4 | import lib.android.paypal.com.magnessdk.Environment 5 | 6 | internal val CoreConfig.magnesEnvironment: Environment 7 | get() = when (environment) { 8 | com.paypal.android.corepayments.Environment.LIVE -> Environment.LIVE 9 | com.paypal.android.corepayments.Environment.SANDBOX -> Environment.SANDBOX 10 | } 11 | -------------------------------------------------------------------------------- /FraudProtection/src/main/java/com/paypal/android/fraudprotection/PayPalDataCollectorRequest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.fraudprotection 2 | 3 | /** 4 | * Request object containing parameters to configure fraud protection data collection. 5 | * 6 | * @property [hasUserLocationConsent] informs the SDK if your application has obtained 7 | * consent from the user to collect location data in compliance with 8 | * 9 | * Google Play Developer Program policies 10 | * This flag enables PayPal to collect necessary information required for Fraud Detection and Risk Management. 11 | * @property [clientMetadataId] forward this data to your server when completing a transaction 12 | * @property [additionalData] additional metadata to link with data collection 13 | */ 14 | data class PayPalDataCollectorRequest @JvmOverloads constructor( 15 | val hasUserLocationConsent: Boolean, 16 | val clientMetadataId: String? = null, 17 | val additionalData: Map? = null, 18 | ) 19 | -------------------------------------------------------------------------------- /FraudProtection/src/main/java/com/paypal/android/fraudprotection/SharedPreferenceUtils.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.fraudprotection 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import androidx.preference.PreferenceManager 6 | 7 | internal class SharedPreferenceUtils { 8 | 9 | companion object { 10 | val instance = SharedPreferenceUtils() 11 | } 12 | 13 | private fun getSharedPreferences(context: Context): SharedPreferences = 14 | PreferenceManager.getDefaultSharedPreferences(context) 15 | 16 | fun putString(context: Context, key: String, value: String) = 17 | getSharedPreferences(context) 18 | .edit() 19 | .putString(key, value) 20 | .apply() 21 | 22 | fun getString(context: Context, key: String, fallback: String?): String? = 23 | getSharedPreferences(context).getString(key, fallback) ?: fallback 24 | } 25 | -------------------------------------------------------------------------------- /FraudProtection/src/main/java/com/paypal/android/fraudprotection/UUIDHelper.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.fraudprotection 2 | 3 | import android.content.Context 4 | import java.util.UUID 5 | 6 | internal class UUIDHelper { 7 | 8 | companion object { 9 | private const val INSTALL_GUID: String = "InstallationGUID" 10 | } 11 | 12 | fun getInstallationGUID(context: Context): String { 13 | val existingGUID = SharedPreferenceUtils.instance.getString(context, INSTALL_GUID, null) 14 | return if (existingGUID != null) { 15 | existingGUID 16 | } else { 17 | val newGuid = UUID.randomUUID().toString() 18 | SharedPreferenceUtils.instance.putString(context, INSTALL_GUID, newGuid) 19 | newGuid 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /FraudProtection/src/test/java/com/paypal/android/fraudprotection/UUIDHelperUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.fraudprotection 2 | 3 | import io.mockk.every 4 | import io.mockk.just 5 | import io.mockk.mockk 6 | import io.mockk.mockkObject 7 | import io.mockk.mockkStatic 8 | import io.mockk.runs 9 | import io.mockk.unmockkAll 10 | import org.junit.After 11 | import org.junit.Test 12 | import org.junit.Assert.assertEquals 13 | import java.util.UUID 14 | 15 | class UUIDHelperUnitTest { 16 | @Test 17 | fun `when appGUID is not set, uuidHelper returns a new one`() { 18 | val mockUUID = "mock-uuid" 19 | mockkObject(SharedPreferenceUtils) 20 | mockkStatic(UUID::class) 21 | every { UUID.randomUUID().toString() } returns mockUUID 22 | every { SharedPreferenceUtils.instance.getString(any(), any(), any()) } returns null 23 | every { SharedPreferenceUtils.instance.putString(any(), any(), any()) } just runs 24 | 25 | val sut = UUIDHelper() 26 | val appGuid = sut.getInstallationGUID(mockk(relaxed = true)) 27 | assertEquals(appGuid, mockUUID) 28 | } 29 | 30 | @Test 31 | fun `when appGUID has been set, uuidHelper returns it`() { 32 | val mockUUID = "mock-uuid" 33 | mockkObject(SharedPreferenceUtils) 34 | every { SharedPreferenceUtils.instance.getString(any(), any(), any()) } returns mockUUID 35 | 36 | val sut = UUIDHelper() 37 | val appGuid = sut.getInstallationGUID(mockk(relaxed = true)) 38 | assertEquals(appGuid, mockUUID) 39 | } 40 | 41 | @After 42 | fun tearDown() { 43 | unmockkAll() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /PayPalWebPayments/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /PayPalWebPayments/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias libs.plugins.android.library 3 | alias libs.plugins.kotlinAndroid 4 | } 5 | 6 | android { 7 | namespace 'com.paypal.android.paypalwebpayments' 8 | java { 9 | toolchain { 10 | languageVersion = modules.kotlinToolchainLanguageVersion 11 | } 12 | } 13 | 14 | defaultConfig { 15 | compileSdk modules.androidCompileSdk 16 | minSdkVersion modules.androidMinSdkVersion 17 | targetSdkVersion modules.androidTargetVersion 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | consumerProguardFiles "consumer-rules.pro" 20 | } 21 | 22 | buildTypes { 23 | release { 24 | version = rootProject.version 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | 30 | compileOptions { 31 | sourceCompatibility modules.sourceCompatibility 32 | targetCompatibility modules.targetCompatibility 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = modules.kotlinJvmTarget 37 | } 38 | 39 | namespace 'com.paypal.android.paypalwebpayments' 40 | } 41 | 42 | dependencies { 43 | api project(':CorePayments') 44 | implementation libs.braintree.browserSwitch 45 | 46 | implementation libs.kotlin.stdLib 47 | implementation libs.androidx.coreKtx 48 | implementation libs.androidx.appcompat 49 | implementation libs.kotlinx.coroutinesAndroid 50 | implementation libs.android.material 51 | 52 | testImplementation libs.junit 53 | testImplementation libs.mockk 54 | testImplementation libs.robolectric 55 | testImplementation libs.kotlinx.coroutinesTest 56 | testImplementation libs.strikt.core 57 | testImplementation libs.strikt.mockk 58 | 59 | androidTestImplementation libs.androidx.test.junit 60 | androidTestImplementation libs.androidx.test.espresso 61 | } 62 | 63 | project.ext.name = "paypal-web-payments" 64 | project.ext.version = rootProject.version 65 | project.ext.pom_name = "PayPal SDK - Web Payments" 66 | project.ext.pom_desc = "Library for PayPal web payments" 67 | 68 | apply from: rootProject.file("gradle/gradle-publish.gradle") -------------------------------------------------------------------------------- /PayPalWebPayments/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/PayPalWebPayments/consumer-rules.pro -------------------------------------------------------------------------------- /PayPalWebPayments/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /PayPalWebPayments/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalPresentAuthChallengeResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.paypalwebpayments 2 | 3 | import com.paypal.android.corepayments.PayPalSDKError 4 | 5 | sealed class PayPalPresentAuthChallengeResult { 6 | data class Success(val authState: String) : PayPalPresentAuthChallengeResult() 7 | data class Failure(val error: PayPalSDKError) : PayPalPresentAuthChallengeResult() 8 | } 9 | -------------------------------------------------------------------------------- /PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutFinishStartResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.paypalwebpayments 2 | 3 | import com.paypal.android.corepayments.PayPalSDKError 4 | 5 | sealed class PayPalWebCheckoutFinishStartResult { 6 | class Success(val orderId: String?, val payerId: String?) : PayPalWebCheckoutFinishStartResult() 7 | class Failure(val error: PayPalSDKError, val orderId: String?) : PayPalWebCheckoutFinishStartResult() 8 | class Canceled(val orderId: String?) : PayPalWebCheckoutFinishStartResult() 9 | data object NoResult : PayPalWebCheckoutFinishStartResult() 10 | } 11 | -------------------------------------------------------------------------------- /PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutFinishVaultResult.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.paypalwebpayments 2 | 3 | import com.paypal.android.corepayments.PayPalSDKError 4 | 5 | sealed class PayPalWebCheckoutFinishVaultResult { 6 | 7 | class Success(val approvalSessionId: String) : PayPalWebCheckoutFinishVaultResult() 8 | class Failure(val error: PayPalSDKError) : PayPalWebCheckoutFinishVaultResult() 9 | data object Canceled : PayPalWebCheckoutFinishVaultResult() 10 | data object NoResult : PayPalWebCheckoutFinishVaultResult() 11 | } 12 | -------------------------------------------------------------------------------- /PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutFundingSource.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.paypalwebpayments 2 | 3 | /** 4 | * Enum class to specify the type of funding for an order. 5 | * For more information go to: https://developer.paypal.com/docs/checkout/pay-later/us/ 6 | */ 7 | enum class PayPalWebCheckoutFundingSource(val value: String) { 8 | /** 9 | * PAYPAL_CREDIT will launch the web checkout flow and display PayPal Credit funding to eligible customers 10 | * Eligible costumers receive a revolving line of credit that they can use to pay over time. 11 | */ 12 | PAYPAL_CREDIT("credit"), 13 | 14 | /** 15 | * PAY_LATER will launch the web checkout flow and display Pay Later offers to eligible customers, 16 | * which include short-term, interest-free payments and other special financing options. 17 | */ 18 | PAY_LATER("paylater"), 19 | 20 | /** 21 | * PAYPAL will launch the web checkout for a one-time PayPal Checkout flow 22 | */ 23 | PAYPAL("paypal") 24 | } 25 | -------------------------------------------------------------------------------- /PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutRequest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.paypalwebpayments 2 | 3 | /** 4 | * Creates an instance of a PayPalRequest. 5 | * 6 | * @param orderId The ID of the order to be approved. 7 | * @param fundingSource specify funding (credit, paylater or default) 8 | */ 9 | data class PayPalWebCheckoutRequest @JvmOverloads constructor( 10 | val orderId: String, 11 | val fundingSource: PayPalWebCheckoutFundingSource = PayPalWebCheckoutFundingSource.PAYPAL 12 | ) 13 | -------------------------------------------------------------------------------- /PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebVaultRequest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.paypalwebpayments 2 | 3 | /** 4 | * Request to vault a PayPal payment method using [PayPalWebCheckoutClient.vault]. 5 | * 6 | * @property [setupTokenId] ID for the setup token associated with the vault approval 7 | * @property [approveVaultHref] URL for the approval web page 8 | */ 9 | data class PayPalWebVaultRequest @Deprecated("Use PayPalWebVaultRequest(setupTokenId) instead.") 10 | constructor( 11 | val setupTokenId: String, 12 | @Deprecated("The approveVaultHref property is no longer required and will be ignored.") 13 | val approveVaultHref: String? // NEXT_MAJOR_VERSION: - Remove this property 14 | ) { 15 | 16 | /** 17 | * Request to vault a PayPal payment method using [PayPalWebCheckoutClient.vault]. 18 | * 19 | * @property [setupTokenId] ID for the setup token associated with the vault approval 20 | */ 21 | constructor(setupTokenId: String) : this(setupTokenId, null) 22 | } 23 | -------------------------------------------------------------------------------- /PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/analytics/CheckoutEvent.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("SpacingAroundParens", "NoMultipleSpaces", "MaxLineLength") 2 | 3 | package com.paypal.android.paypalwebpayments.analytics 4 | 5 | internal enum class CheckoutEvent(val value: String) { 6 | // @formatter:off 7 | STARTED( "paypal-web-payments:checkout:started"), 8 | SUCCEEDED("paypal-web-payments:checkout:succeeded"), 9 | FAILED( "paypal-web-payments:checkout:failed"), 10 | CANCELED( "paypal-web-payments:checkout:canceled"), 11 | 12 | AUTH_CHALLENGE_PRESENTATION_SUCCEEDED("paypal-web-payments:checkout:auth-challenge-presentation:succeeded"), 13 | AUTH_CHALLENGE_PRESENTATION_FAILED( "paypal-web-payments:checkout:auth-challenge-presentation:failed"), 14 | // @formatter:on 15 | } 16 | -------------------------------------------------------------------------------- /PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/analytics/PayPalWebAnalytics.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.paypalwebpayments.analytics 2 | 3 | import com.paypal.android.corepayments.analytics.AnalyticsService 4 | 5 | internal class PayPalWebAnalytics(private val analyticsService: AnalyticsService) { 6 | 7 | fun notify(event: CheckoutEvent, orderId: String?) { 8 | analyticsService.sendAnalyticsEvent(event.value, orderId) 9 | } 10 | 11 | fun notify(event: VaultEvent, setupTokenId: String?) { 12 | analyticsService.sendAnalyticsEvent(event.value, setupTokenId) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/analytics/VaultEvent.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("SpacingAroundParens", "NoMultipleSpaces", "MaxLineLength") 2 | 3 | package com.paypal.android.paypalwebpayments.analytics 4 | 5 | internal enum class VaultEvent(val value: String) { 6 | // @formatter:off 7 | STARTED( "paypal-web-payments:vault-wo-purchase:started"), 8 | SUCCEEDED("paypal-web-payments:vault-wo-purchase:succeeded"), 9 | FAILED( "paypal-web-payments:vault-wo-purchase:failed"), 10 | CANCELED( "paypal-web-payments:vault-wo-purchase:canceled"), 11 | 12 | AUTH_CHALLENGE_PRESENTATION_SUCCEEDED("paypal-web-payments:vault-wo-purchase:auth-challenge-presentation:succeeded"), 13 | AUTH_CHALLENGE_PRESENTATION_FAILED( "paypal-web-payments:vault-wo-purchase:auth-challenge-presentation:failed"), 14 | // @formatter:on 15 | } 16 | -------------------------------------------------------------------------------- /PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/errors/PayPalWebCheckoutError.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.paypalwebpayments.errors 2 | 3 | import com.paypal.android.corepayments.PayPalSDKError 4 | 5 | internal object PayPalWebCheckoutError { 6 | 7 | // 0. An unknown error occurred. 8 | val unknownError = PayPalSDKError( 9 | code = PayPalWebCheckoutErrorCode.UNKNOWN.ordinal, 10 | errorDescription = "An unknown error occurred. Contact developer.paypal.com/support." 11 | ) 12 | 13 | // 1. Result did not contain the expected data. 14 | val malformedResultError = PayPalSDKError( 15 | code = PayPalWebCheckoutErrorCode.MALFORMED_RESULT.ordinal, 16 | errorDescription = "Result did not contain the expected data. Payer ID or Order ID is null." 17 | ) 18 | 19 | // 2. An error occurred while browser switching 20 | fun browserSwitchError(cause: Exception) = PayPalSDKError( 21 | code = PayPalWebCheckoutErrorCode.BROWSER_SWITCH.ordinal, 22 | errorDescription = cause.message ?: "Unable to Browser Switch" 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/errors/PayPalWebCheckoutErrorCode.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.paypalwebpayments.errors 2 | 3 | internal enum class PayPalWebCheckoutErrorCode { 4 | UNKNOWN, 5 | MALFORMED_RESULT, 6 | BROWSER_SWITCH 7 | } 8 | -------------------------------------------------------------------------------- /PayPalWebPayments/src/test/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutRequestUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.paypalwebpayments 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | class PayPalWebCheckoutRequestUnitTest { 7 | 8 | @Test 9 | fun `given an order id, PayPalRequest should return the same orderId`() { 10 | val orderId = "fake_order_id" 11 | val payPalRequest = PayPalWebCheckoutRequest(orderId) 12 | assertEquals(orderId, payPalRequest.orderId) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PayPalWebPayments/src/test/resources/robolectric.properties: -------------------------------------------------------------------------------- 1 | # TODO: remove this file when Robolectric supports API level 35 (Android 15) 2 | sdk=33 3 | -------------------------------------------------------------------------------- /PaymentButtons/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias libs.plugins.android.library 3 | alias libs.plugins.kotlinAndroid 4 | } 5 | 6 | android { 7 | namespace 'com.paypal.android.ui' 8 | java { 9 | toolchain { 10 | languageVersion = modules.kotlinToolchainLanguageVersion 11 | } 12 | } 13 | 14 | defaultConfig { 15 | compileSdk modules.androidCompileSdk 16 | minSdkVersion modules.androidMinSdkVersion 17 | targetSdkVersion modules.androidTargetVersion 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | consumerProguardFiles "consumer-rules.pro" 20 | } 21 | 22 | buildTypes { 23 | release { 24 | version = rootProject.version 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | 30 | compileOptions { 31 | sourceCompatibility modules.sourceCompatibility 32 | targetCompatibility modules.targetCompatibility 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = modules.kotlinJvmTarget 37 | } 38 | 39 | namespace 'com.paypal.android.ui' 40 | } 41 | 42 | dependencies { 43 | implementation libs.androidx.coreKtx 44 | implementation libs.android.material 45 | 46 | implementation project(':CorePayments') 47 | } 48 | 49 | project.ext.name = "payment-buttons" 50 | project.ext.version = rootProject.version 51 | project.ext.pom_name = "PayPal SDK - Payment Buttons" 52 | project.ext.pom_desc = "Library for PayPal's payment buttons and UI components" 53 | 54 | apply from: rootProject.file("gradle/gradle-publish.gradle") -------------------------------------------------------------------------------- /PaymentButtons/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/PaymentButtons/consumer-rules.pro -------------------------------------------------------------------------------- /PaymentButtons/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /PaymentButtons/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/java/com/paypal/android/paymentbuttons/PaymentButtonColor.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.paymentbuttons 2 | 3 | import android.content.Context 4 | import android.content.res.ColorStateList 5 | import androidx.core.content.ContextCompat 6 | 7 | /** 8 | * PaymentButtonColor provides a structure for all colors that can be used within a [PaymentButton]. 9 | * 10 | * @property colorResId is the color resource ID that can be used for displaying the color. 11 | * @property hasOutline when true the color should be displayed with an outline, when false then 12 | * no outline will be drawn. 13 | * @property luminance defines the luminance of the color, useful when determining which type of 14 | * wordmark to use or when determining the surface color of text widgets on the button. 15 | */ 16 | interface PaymentButtonColor { 17 | val colorResId: Int 18 | 19 | val hasOutline: Boolean 20 | 21 | val luminance: PaymentButtonColorLuminance 22 | 23 | /** 24 | * Provides the correct [ColorStateList] given a [Context]. 25 | * 26 | * @return [ColorStateList] which corresponds to the [PayPalButtonColor]. 27 | */ 28 | fun retrieveColorResource(context: Context): ColorStateList { 29 | return ContextCompat.getColorStateList(context, colorResId)!! 30 | } 31 | } 32 | 33 | /** 34 | * ColorLuminance defines the intensity of the light emitted by a given color with simplified 35 | * [DARK] and [LIGHT] values. 36 | * 37 | * This is helpful when modifying internal button styles (wordmark and text for example) to 38 | * change those colors based on the button's background color while still allow for exhaustive 39 | * compile time checks (something not currently possible with booleans). 40 | */ 41 | enum class PaymentButtonColorLuminance { 42 | DARK, LIGHT; 43 | } 44 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/java/com/paypal/android/paymentbuttons/PaymentButtonShape.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.paymentbuttons 2 | 3 | import com.paypal.android.paymentbuttons.error.createFormattedIllegalArgumentException 4 | 5 | /** 6 | * Defines the shapes available for payment buttons. If no shape is provided then the default 7 | * button style will be retrieved from the applications root style. 8 | * 9 | * @see ROUNDED will render the button with rounded corners. 10 | * @see PILL will render the button with circular corners on either side, it looks like a pill. 11 | * @see RECTANGLE will render the button with sharp corners, it will look like a rectangle. 12 | */ 13 | enum class PaymentButtonShape(val value: Int) { 14 | ROUNDED(value = 0), 15 | PILL(value = 1), 16 | RECTANGLE(value = 2); 17 | 18 | companion object { 19 | /** 20 | * Given an [attributeIndex] this will provide the correct [PaymentButtonShape]. If an 21 | * invalid [attributeIndex] is provided then it will throw an [IllegalArgumentException]. 22 | * 23 | * @throws [IllegalArgumentException] when an invalid index is provided. 24 | */ 25 | operator fun invoke(attributeIndex: Int): PaymentButtonShape { 26 | return when (attributeIndex) { 27 | ROUNDED.value -> ROUNDED 28 | PILL.value -> PILL 29 | RECTANGLE.value -> RECTANGLE 30 | else -> throw createFormattedIllegalArgumentException("PaymentButtonShape", values().size) 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/java/com/paypal/android/paymentbuttons/error/Exceptions.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.paymentbuttons.error 2 | 3 | internal fun createFormattedIllegalArgumentException(enumName: String, enumValues: Int): IllegalArgumentException { 4 | val exceptionMessage = "Attempted to create a $enumName with an invalid index. Please use an" + 5 | " index that is between 0 and ${enumValues - 1} and try again." 6 | return IllegalArgumentException(exceptionMessage) 7 | } 8 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/color/paypal_black.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/color/paypal_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/color/paypal_dark_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/color/paypal_gold.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/color/paypal_silver.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/color/paypal_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/drawable/logo_paypal_color.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 24 | 25 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/drawable/logo_paypal_monochrome.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 24 | 25 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/font/paypalopen_regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/PaymentButtons/src/main/res/font/paypalopen_regular.otf -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/layout/paypal_ui_payment_button_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 22 | 23 | 31 | 32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #8f000000 4 | 5 | #000000 6 | #FFFFFF 7 | #000000 8 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8dp 4 | 5 | 6 | 4dp 7 | 0dp 8 | 24dp 9 | @dimen/margin_8x 10 | 11 | 1dp 12 | 13 | 48dp 14 | 55dp 15 | 17 | 13dp 18 | 15dp 19 | 14sp 20 | 18sp 21 | 16dp 22 | 23 | -------------------------------------------------------------------------------- /PaymentButtons/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PayPal Button 5 | PayPal Credit Button 6 | Buy Now 7 | Checkout 8 | Pay with 9 | Pay Later 10 | -------------------------------------------------------------------------------- /Venmo/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias libs.plugins.android.library 3 | alias libs.plugins.kotlinAndroid 4 | } 5 | 6 | android { 7 | namespace 'com.paypal.android.venmo' 8 | java { 9 | toolchain { 10 | languageVersion = modules.kotlinToolchainLanguageVersion 11 | } 12 | } 13 | 14 | defaultConfig { 15 | compileSdk modules.androidCompileSdk 16 | minSdkVersion modules.androidMinSdkVersion 17 | targetSdkVersion modules.androidTargetVersion 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | consumerProguardFiles "consumer-rules.pro" 20 | } 21 | 22 | buildTypes { 23 | release { 24 | version = rootProject.version 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | 30 | compileOptions { 31 | sourceCompatibility modules.sourceCompatibility 32 | targetCompatibility modules.targetCompatibility 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = modules.kotlinJvmTarget 37 | } 38 | namespace 'com.paypal.android.venmo' 39 | } 40 | 41 | dependencies { 42 | implementation libs.androidx.coreKtx 43 | implementation libs.androidx.appcompat 44 | implementation libs.android.material 45 | 46 | testImplementation libs.junit 47 | 48 | androidTestImplementation libs.androidx.test.junit 49 | androidTestImplementation libs.androidx.test.espresso 50 | } 51 | -------------------------------------------------------------------------------- /Venmo/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/Venmo/consumer-rules.pro -------------------------------------------------------------------------------- /Venmo/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /Venmo/src/androidTest/java/com/paypal/android/venmo/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.venmo 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.paypal.android.venmo.test", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Venmo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Venmo/src/test/java/com/paypal/android/venmo/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.paypal.android.venmo 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /adr/1-remove-graphql-generics/figure-graph-ql-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/adr/1-remove-graphql-generics/figure-graph-ql-client.png -------------------------------------------------------------------------------- /adr/1-remove-graphql-generics/figure-payments-sdk-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/adr/1-remove-graphql-generics/figure-payments-sdk-architecture.png -------------------------------------------------------------------------------- /adr/1-remove-graphql-generics/figure-query-abstract-base-class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/adr/1-remove-graphql-generics/figure-query-abstract-base-class.png -------------------------------------------------------------------------------- /adr/2-remove-core-api-and-http-request-factories/figure-card-client-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/adr/2-remove-core-api-and-http-request-factories/figure-card-client-example.png -------------------------------------------------------------------------------- /adr/2-remove-core-api-and-http-request-factories/figure-deep-module-vs-shallow-module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/adr/2-remove-core-api-and-http-request-factories/figure-deep-module-vs-shallow-module.png -------------------------------------------------------------------------------- /adr/2-remove-core-api-and-http-request-factories/figure-multi-api-uml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/adr/2-remove-core-api-and-http-request-factories/figure-multi-api-uml.png -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | 4 | coverage: 5 | status: 6 | project: off 7 | patch: off 8 | 9 | parsers: 10 | gcov: 11 | branch_detection: 12 | conditional: yes 13 | loop: yes 14 | method: no 15 | macro: no 16 | 17 | comment: 18 | layout: "reach,diff,flags,files,footer" 19 | behavior: default 20 | require_changes: no 21 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | 21 | POM_PACKAGING=aar 22 | #TODO: needs to be defined 23 | POM_URL=not_defined 24 | 25 | POM_SCM_URL=https://github.com/paypal/paypal-android 26 | POM_SCM_CONNECTION=scm:git@github.com:paypal/paypal-android.git 27 | POM_SCM_DEV_CONNECTION=scm:git@github.com:paypal/paypal-android.git 28 | 29 | POM_LICENCE_NAME=The Apache License, Version 2.0 30 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0 31 | 32 | POM_DEVELOPER_ID=paypal-android 33 | POM_DEVELOPER_NAME=PayPal Android 34 | #TODO: needs to be defined 35 | POM_DEVELOPER_EMAIL=not_defined 36 | 37 | android.defaults.buildfeatures.buildconfig=true 38 | android.nonTransitiveRClass=false 39 | android.nonFinalResIds=false 40 | 41 | -------------------------------------------------------------------------------- /gradle/gradle-publish.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | apply plugin: 'signing' 3 | 4 | ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') ?: '' 5 | ext["signing.password"] = System.getenv('SIGNING_KEY_PASSWORD') ?: '' 6 | ext["signing.secretKeyRingFile"] = System.getenv('SIGNING_KEY_FILE') ?: '' 7 | 8 | android { 9 | publishing { 10 | singleVariant('release') { 11 | withSourcesJar() 12 | } 13 | } 14 | } 15 | 16 | publishing { 17 | publications { 18 | release(MavenPublication) { 19 | groupId group 20 | version project.ext.version 21 | artifactId project.ext.name 22 | 23 | afterEvaluate { 24 | from components.release 25 | } 26 | 27 | pom { 28 | name = project.ext.pom_name ?: '' 29 | packaging = POM_PACKAGING 30 | description = project.ext.pom_desc ?: '' 31 | url = POM_URL 32 | licenses { 33 | license { 34 | name = POM_LICENCE_NAME 35 | url = POM_LICENCE_URL 36 | } 37 | } 38 | developers { 39 | developer { 40 | id = POM_DEVELOPER_ID 41 | name = POM_DEVELOPER_NAME 42 | email = POM_DEVELOPER_EMAIL 43 | } 44 | } 45 | scm { 46 | connection = POM_SCM_CONNECTION 47 | developerConnection = POM_SCM_DEV_CONNECTION 48 | url = POM_SCM_URL 49 | } 50 | } 51 | } 52 | } 53 | 54 | signing { 55 | sign publishing.publications.release 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/paypal-android/9d77a9e760518e586a64c135ad7f225e4293fb20/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jun 21 09:49:22 CDT 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | // TODO: Remove this ignore when SDK has been tested against API 31 6 | 7 | // TODO: Remove this once Self Service Android Studio Version has been updated 8 | 9 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google { 4 | content { 5 | includeGroupByRegex("com\\.android.*") 6 | includeGroupByRegex("com\\.google.*") 7 | includeGroupByRegex("androidx.*") 8 | } 9 | } 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | } 14 | 15 | dependencyResolutionManagement { 16 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 17 | repositories { 18 | google() 19 | mavenCentral() 20 | mavenLocal() 21 | maven { 22 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 23 | } 24 | } 25 | } 26 | 27 | rootProject.name = "AndroidSDK" 28 | include ':CardPayments' 29 | include ':CorePayments' 30 | include ':Demo' 31 | include ':FraudProtection' 32 | include ':PayPalWebPayments' 33 | include ':PaymentButtons' 34 | include ':Venmo' 35 | --------------------------------------------------------------------------------