diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 27128827..c7b22198 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: uses: actions/cache@v4 with: path: node_modules - key: ${{ runner.os }}-node-${{ steps.setup_node.outputs.node-version }}-npm-${{ hashFiles('**/package-lock.json') }}-v1 + key: ${{ runner.os }}-node-${{ steps.setup_node.outputs.node-version }}-npm-${{ hashFiles('**/package-lock.json') }}-v2 - name: Build Payment package run: npm install && npm run build @@ -57,7 +57,7 @@ jobs: - name: Build Android Debug APK working-directory: ./demo/angular/android - run: ./gradlew assembleDebug + run: ./gradlew assembleDebug --stacktrace - name: Upload a Build Artifact uses: actions/upload-artifact@v4 @@ -138,7 +138,7 @@ jobs: working-directory: ./packages/terminal verify-payment-ios: - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 @@ -157,7 +157,7 @@ jobs: working-directory: ./packages/payment verify-identity-ios: - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 @@ -176,7 +176,7 @@ jobs: working-directory: ./packages/identity verify-terminal-ios: - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 diff --git a/README.md b/README.md index 0b4d3ed0..bb410b39 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ To use the latest Stripe Android, you need to version these up. To use the lates + identityVersion = '21.3.+' // If you use @simPRO-Software/stripe-terminal: -+ stripeterminalCoreVersion = '4.1.0' -+ stripeterminalTapToPayVersion = '4.1.0' ++ stripeterminalCoreVersion = '4.5.0' ++ stripeterminalTapToPayVersion = '4.5.0' } ``` diff --git a/demo/angular/android/app/src/main/assets/capacitor.config.json b/demo/angular/android/app/src/main/assets/capacitor.config.json index 134a984c..824c6aeb 100644 --- a/demo/angular/android/app/src/main/assets/capacitor.config.json +++ b/demo/angular/android/app/src/main/assets/capacitor.config.json @@ -1,6 +1,5 @@ { "appId": "io.ionic.starter", "appName": "capacitor-stripe", - "webDir": "www/browser", - "bundledWebRuntime": false + "webDir": "www/browser" } diff --git a/demo/angular/angular.json b/demo/angular/angular.json index 61b0ea47..9b57c673 100644 --- a/demo/angular/angular.json +++ b/demo/angular/angular.json @@ -11,7 +11,7 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular/build:application", "options": { "outputPath": "www", "index": "src/index.html", @@ -71,7 +71,7 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "configurations": { "production": { "buildTarget": "app:build:production" @@ -86,13 +86,13 @@ "defaultConfiguration": "development" }, "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", + "builder": "@angular/build:extract-i18n", "options": { "buildTarget": "app:build" } }, "test": { - "builder": "@angular-devkit/build-angular:karma", + "builder": "@angular/build:karma", "options": { "main": "src/test.ts", "polyfills": "src/polyfills.ts", @@ -148,6 +148,30 @@ }, "@ionic/angular-toolkit:page": { "styleext": "scss" + }, + "@schematics/angular:component": { + "type": "component" + }, + "@schematics/angular:directive": { + "type": "directive" + }, + "@schematics/angular:service": { + "type": "service" + }, + "@schematics/angular:guard": { + "typeSeparator": "." + }, + "@schematics/angular:interceptor": { + "typeSeparator": "." + }, + "@schematics/angular:module": { + "typeSeparator": "." + }, + "@schematics/angular:pipe": { + "typeSeparator": "." + }, + "@schematics/angular:resolver": { + "typeSeparator": "." } } } diff --git a/demo/angular/ios/App/App/capacitor.config.json b/demo/angular/ios/App/App/capacitor.config.json index 951b875a..7a14ecd1 100644 --- a/demo/angular/ios/App/App/capacitor.config.json +++ b/demo/angular/ios/App/App/capacitor.config.json @@ -2,7 +2,6 @@ "appId": "io.ionic.starter", "appName": "capacitor-stripe", "webDir": "www/browser", - "bundledWebRuntime": false, "packageClassList": [ "StripePlugin", "StripeIdentityPlugin", diff --git a/demo/angular/ios/App/Podfile.lock b/demo/angular/ios/App/Podfile.lock new file mode 100644 index 00000000..80cdfc94 --- /dev/null +++ b/demo/angular/ios/App/Podfile.lock @@ -0,0 +1,91 @@ +PODS: + - Capacitor (7.0.1): + - CapacitorCordova + - CapacitorCommunityStripe (7.2.1): + - Capacitor + - StripeApplePay (~> 25.1.0) + - StripePaymentSheet (~> 25.1.0) + - CapacitorCommunityStripeIdentity (7.2.1): + - Capacitor + - StripeIdentity (~> 25.1.0) + - CapacitorCommunityStripeTerminal (7.2.1): + - Capacitor + - StripeTerminal (= 4.7.3) + - CapacitorCordova (7.0.1) + - StripeApplePay (25.1.0): + - StripeCore (= 25.1.0) + - StripeCameraCore (25.1.0): + - StripeCore (= 25.1.0) + - StripeCore (25.1.0) + - StripeIdentity (25.1.0): + - StripeCameraCore (= 25.1.0) + - StripeCore (= 25.1.0) + - StripeUICore (= 25.1.0) + - StripePayments (25.1.0): + - StripeCore (= 25.1.0) + - StripePayments/Stripe3DS2 (= 25.1.0) + - StripePayments/Stripe3DS2 (25.1.0): + - StripeCore (= 25.1.0) + - StripePaymentSheet (25.1.0): + - StripeApplePay (= 25.1.0) + - StripeCore (= 25.1.0) + - StripePayments (= 25.1.0) + - StripePaymentsUI (= 25.1.0) + - StripePaymentsUI (25.1.0): + - StripeCore (= 25.1.0) + - StripePayments (= 25.1.0) + - StripeUICore (= 25.1.0) + - StripeTerminal (4.7.3) + - StripeUICore (25.1.0): + - StripeCore (= 25.1.0) + +DEPENDENCIES: + - "Capacitor (from `../../node_modules/@capacitor/ios`)" + - CapacitorCommunityStripe (from `../../../../packages/payment`) + - CapacitorCommunityStripeIdentity (from `../../../../packages/identity`) + - CapacitorCommunityStripeTerminal (from `../../../../packages/terminal`) + - "CapacitorCordova (from `../../node_modules/@capacitor/ios`)" + +SPEC REPOS: + trunk: + - StripeApplePay + - StripeCameraCore + - StripeCore + - StripeIdentity + - StripePayments + - StripePaymentSheet + - StripePaymentsUI + - StripeTerminal + - StripeUICore + +EXTERNAL SOURCES: + Capacitor: + :path: "../../node_modules/@capacitor/ios" + CapacitorCommunityStripe: + :path: "../../../../packages/payment" + CapacitorCommunityStripeIdentity: + :path: "../../../../packages/identity" + CapacitorCommunityStripeTerminal: + :path: "../../../../packages/terminal" + CapacitorCordova: + :path: "../../node_modules/@capacitor/ios" + +SPEC CHECKSUMS: + Capacitor: de199cba6c8b20995428ad0b7cb0bc6ca625ffd4 + CapacitorCommunityStripe: 684052203ba8abd2cf74b464df0603ab3e6e464d + CapacitorCommunityStripeIdentity: f8dbf6be3961097ba7b3075258776bccc3ec2f81 + CapacitorCommunityStripeTerminal: 23da4329032aa23d3bc40b630a36dc978f664496 + CapacitorCordova: 63d476958d5022d76f197031e8b7ea3519988c64 + StripeApplePay: baa0634219c168df6a50c7d76ae0d9bff39dbaf9 + StripeCameraCore: e12d90fd62abcf5a1cb9bb8a85d72e5cbb7e7666 + StripeCore: 6078969b9c9b205081bc8d66991e2a6d4bd3e994 + StripeIdentity: 7d40804f948a38e6c99fa53d0400a3d955758a00 + StripePayments: b80892b98f25f4ee890773755f2940727aefcb57 + StripePaymentSheet: 77673b324a5cc5c9327f5c8cfcdf13d2f6495dde + StripePaymentsUI: 057811da34b69e89a06ec1962fffe4247b98ee92 + StripeTerminal: b9e92913da8a08ff5c62af75056ad39bf8b02ac6 + StripeUICore: 4f47fdd3895f23c6e02879ef27126f62af7058ff + +PODFILE CHECKSUM: c75ca255b97ee461832b20e6cdf6d1da533d4019 + +COCOAPODS: 1.16.2 diff --git a/demo/angular/karma.conf.js b/demo/angular/karma.conf.js index 49eb12fa..51268534 100644 --- a/demo/angular/karma.conf.js +++ b/demo/angular/karma.conf.js @@ -10,7 +10,7 @@ module.exports = function (config) { require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage'), - require('@angular-devkit/build-angular/plugins/karma') + ], client: { jasmine: { diff --git a/demo/angular/package.json b/demo/angular/package.json index 62ea632f..e5efed6b 100644 --- a/demo/angular/package.json +++ b/demo/angular/package.json @@ -15,52 +15,50 @@ }, "private": true, "dependencies": { - "@angular/common": "^18.2.10", - "@angular/core": "^18.2.10", - "@angular/forms": "^18.2.10", - "@angular/platform-browser": "^18.2.10", - "@angular/platform-browser-dynamic": "^18.2.10", - "@angular/router": "^18.2.10", + "@angular/common": "^20.3.12", + "@angular/core": "^20.3.12", + "@angular/forms": "^20.3.12", + "@angular/platform-browser": "^20.3.12", + "@angular/platform-browser-dynamic": "^20.3.12", + "@angular/router": "^20.3.12", "@capacitor-community/stripe": "file:../../packages/payment", "@capacitor-community/stripe-identity": "file:../../packages/identity", - "@simPRO-Software/stripe-terminal": "file:../../packages/terminal", - "@capacitor/android": "^6.0.0", - "@capacitor/core": "^6.0.0", - "@capacitor/ios": "^6.0.0", - "@ionic/angular": "^8.2.6", + "@capacitor-community/stripe-terminal": "file:../../packages/terminal", + "@capacitor/android": "^7.0.0", + "@capacitor/core": "^7.0.0", + "@capacitor/ios": "^7.0.0", + "@ionic/angular": "^8.7.3", "rxjs": "~7.5.0", "stripe-pwa-elements": "^2.1.0", "tslib": "^2.3.0", "zone.js": "~0.14.4" }, "devDependencies": { - "@angular-devkit/build-angular": "^18.2.11", - "@angular-eslint/builder": "~18.4.0", - "@angular-eslint/eslint-plugin": "~18.4.0", - "@angular-eslint/eslint-plugin-template": "~18.4.0", - "@angular-eslint/schematics": "~18.4.0", - "@angular-eslint/template-parser": "~18.4.0", - "@angular/cli": "^18.2.11", - "@angular/compiler": "^18.2.10", - "@angular/compiler-cli": "^18.2.10", - "@angular/language-service": "^18.2.10", - "@capacitor/cli": "6.0.0", + "@angular/build": "^20.3.10", + "@angular/cli": "^20.3.10", + "@angular/compiler": "^20.3.12", + "@angular/compiler-cli": "^20.3.12", + "@angular/language-service": "^20.3.12", + "@capacitor/cli": "^7.0.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "^9.25.1", "@ionic/angular-toolkit": "^10.0.0", "@types/jasmine": "~3.6.0", "@types/jasminewd2": "~2.0.3", - "@types/node": "^12.11.1", - "eslint": "^9.23.0", - "jasmine-core": "~3.8.0", + "angular-eslint": "20.6.0", + "eslint": "^9.28.0", + "jasmine-core": "~4.6.0", "jasmine-spec-reporter": "~5.0.0", - "karma": "~6.3.16", - "karma-chrome-launcher": "~3.1.0", - "karma-coverage": "~2.0.3", + "karma": "^6.4.0", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.0", "karma-coverage-istanbul-reporter": "~3.0.2", - "karma-jasmine": "~4.0.0", - "karma-jasmine-html-reporter": "^1.5.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", "prettier": "^3.5.3", "protractor": "~7.0.0", - "typescript": "~5.4.3" + "typescript": "5.8.3", + "typescript-eslint": "^8.33.1" }, "description": "An Ionic project" } diff --git a/demo/angular/src/app/demo/demo.page.ts b/demo/angular/src/app/demo/demo.page.ts index 9cfc5e33..6344dc45 100644 --- a/demo/angular/src/app/demo/demo.page.ts +++ b/demo/angular/src/app/demo/demo.page.ts @@ -1,5 +1,6 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, inject } from '@angular/core'; import { + Address, ApplePayEventsEnum, CreatePaymentSheetOption, GooglePayEventsEnum, @@ -38,6 +39,8 @@ import { ], }) export class DemoPage implements OnInit { + private http = inject(HttpClient); + processSheet: 'willReady' | 'Ready' = 'willReady'; processFlow: 'willReady' | 'Ready' | 'canConfirm' = 'willReady'; processApplePay: 'willReady' | 'Ready' = 'willReady'; @@ -45,7 +48,10 @@ export class DemoPage implements OnInit { isApplePayAvailable = false; isGooglePayAvailable = false; - constructor(private http: HttpClient) {} + /** Inserted by Angular inject() migration for backwards compatibility */ + constructor(...args: unknown[]); + + constructor() {} async ngOnInit() { Stripe.addListener(PaymentSheetEventsEnum.Loaded, () => { @@ -192,6 +198,25 @@ export class DemoPage implements OnInit { customerEphemeralKeySecret: ephemeralKey, customerId: customer, merchantDisplayName: 'rdlabo', + billingDetailsCollectionConfiguration: { + email: 'always', + name: 'always', + phone: 'always', + address: 'full', + }, + defaultBillingDetails: { + email: 'info@example.com', + name: 'Masahiko Sakakibara', + phone: '+15551234567', + address: { + city: 'San Francisco', + country: 'US', + line1: '123 Market St', + line2: '', + postalCode: '94107', + state: 'CA', + } + } }); } else { const { paymentIntent } = await firstValueFrom( diff --git a/demo/angular/src/app/flow/flow.page.ts b/demo/angular/src/app/flow/flow.page.ts index 3f6ced19..1da273d1 100644 --- a/demo/angular/src/app/flow/flow.page.ts +++ b/demo/angular/src/app/flow/flow.page.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { ITestItems } from '../shared/interfaces'; import { PaymentFlowEventsEnum, Stripe } from '@capacitor-community/stripe'; import { PluginListenerHandle } from '@capacitor/core'; @@ -100,13 +100,16 @@ const cancelPathItems: ITestItems[] = [ ], }) export class FlowPage { + private http = inject(HttpClient); + private helper = inject(HelperService); + public eventItems: ITestItems[] = []; private readonly listenerHandlers: PluginListenerHandle[] = []; - constructor( - private http: HttpClient, - private helper: HelperService, - ) { + /** Inserted by Angular inject() migration for backwards compatibility */ + constructor(...args: unknown[]); + + constructor() { addIcons({ playOutline, notificationsCircleOutline, checkmarkCircle }); } diff --git a/demo/angular/src/app/identity/identity.page.ts b/demo/angular/src/app/identity/identity.page.ts index 7d3f736e..57f58cb8 100644 --- a/demo/angular/src/app/identity/identity.page.ts +++ b/demo/angular/src/app/identity/identity.page.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { ITestItems } from '../shared/interfaces'; import { IdentityVerificationSheetEventsEnum, @@ -45,6 +45,11 @@ const happyPathItems: ITestItems[] = [ name: 'presentIdentityVerificationSheet', expect: [IdentityVerificationSheetEventsEnum.Completed], }, + { + type: 'event', + name: IdentityVerificationSheetEventsEnum.VerificationResult, + expect: IdentityVerificationSheetEventsEnum.Completed, + }, { type: 'event', name: IdentityVerificationSheetEventsEnum.Completed, @@ -69,6 +74,11 @@ const cancelPathItems: ITestItems[] = [ name: 'presentIdentityVerificationSheet', expect: [IdentityVerificationSheetEventsEnum.Canceled], }, + { + type: 'event', + name: IdentityVerificationSheetEventsEnum.VerificationResult, + expect: IdentityVerificationSheetEventsEnum.Canceled, + }, { type: 'event', name: IdentityVerificationSheetEventsEnum.Canceled, @@ -92,13 +102,16 @@ const cancelPathItems: ITestItems[] = [ ], }) export class IdentityPage { + private http = inject(HttpClient); + private helper = inject(HelperService); + public eventItems: ITestItems[] = []; private readonly listenerHandlers: PluginListenerHandle[] = []; - constructor( - private http: HttpClient, - private helper: HelperService, - ) { + /** Inserted by Angular inject() migration for backwards compatibility */ + constructor(...args: unknown[]); + + constructor() { addIcons({ playOutline, notificationsCircleOutline, checkmarkCircle }); } diff --git a/demo/angular/src/app/shared/helper.service.ts b/demo/angular/src/app/shared/helper.service.ts index 160a3893..b114ad72 100644 --- a/demo/angular/src/app/shared/helper.service.ts +++ b/demo/angular/src/app/shared/helper.service.ts @@ -1,11 +1,16 @@ -import { Injectable, NgZone } from '@angular/core'; +import { Injectable, NgZone, inject } from '@angular/core'; import { ITestItems } from './interfaces'; @Injectable({ providedIn: 'root', }) export class HelperService { - constructor(private zone: NgZone) {} + private zone = inject(NgZone); + + /** Inserted by Angular inject() migration for backwards compatibility */ + constructor(...args: unknown[]); + + constructor() {} /** * items is not Deep Copy, this is substitution diff --git a/demo/angular/src/app/sheet/sheet.page.ts b/demo/angular/src/app/sheet/sheet.page.ts index 91528c95..84901ae0 100644 --- a/demo/angular/src/app/sheet/sheet.page.ts +++ b/demo/angular/src/app/sheet/sheet.page.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { PaymentSheetEventsEnum, Stripe } from '@capacitor-community/stripe'; import { ITestItems } from '../shared/interfaces'; import { environment } from '../../environments/environment'; @@ -89,13 +89,16 @@ const cancelPathItems: ITestItems[] = [ ], }) export class SheetPage { + private http = inject(HttpClient); + private helper = inject(HelperService); + public eventItems: ITestItems[] = []; private readonly listenerHandlers: PluginListenerHandle[] = []; - constructor( - private http: HttpClient, - private helper: HelperService, - ) { + /** Inserted by Angular inject() migration for backwards compatibility */ + constructor(...args: unknown[]); + + constructor() { addIcons({ playOutline, notificationsCircleOutline, checkmarkCircle }); } diff --git a/demo/angular/src/app/terminal/terminal.page.ts b/demo/angular/src/app/terminal/terminal.page.ts index 068e5d3f..88dff065 100644 --- a/demo/angular/src/app/terminal/terminal.page.ts +++ b/demo/angular/src/app/terminal/terminal.page.ts @@ -60,6 +60,9 @@ import { updateDeviceUpdateItems } from './updateDeviceUpdateItems'; ], }) export class TerminalPage { + private http = inject(HttpClient); + private helper = inject(HelperService); + public eventItems: ITestItems[] = []; public terminalConnectTypes = TerminalConnectTypes; public simulateReaderUpdate = SimulateReaderUpdate; @@ -68,10 +71,10 @@ export class TerminalPage { public readonly platform = inject(Platform); private readonly alertCtrl = inject(AlertController); - constructor( - private http: HttpClient, - private helper: HelperService, - ) { + /** Inserted by Angular inject() migration for backwards compatibility */ + constructor(); + + constructor() { addIcons({ playOutline, notificationsCircleOutline, checkmarkCircle }); } @@ -226,7 +229,7 @@ export class TerminalPage { await StripeTerminal.discoverReaders({ type: readerType, - locationId: [TerminalConnectTypes.Usb].includes(readerType) + locationId: [TerminalConnectTypes.Usb, TerminalConnectTypes.Bluetooth].includes(readerType) ? 'tml_Ff37mAmk1XdBYT' // : 'tml_FOUOdQVIxvVdvN', : 'tml_FzOLdgFjxlDQbh', @@ -252,9 +255,10 @@ export class TerminalPage { await StripeTerminal.discoverReaders({ type: readerType, - locationId: [TerminalConnectTypes.Usb].includes(readerType) + locationId: [TerminalConnectTypes.Usb, TerminalConnectTypes.Bluetooth].includes(readerType) ? 'tml_Ff37mAmk1XdBYT' // Auckland, New Zealand : 'tml_FOUOdQVIxvVdvN', // San Francisco, CA 94110 + bluetoothScanWaitTime: 2000, }).catch((e) => { this.helper.updateItem(this.eventItems, 'discoverReaders', false); throw e; @@ -381,7 +385,7 @@ export class TerminalPage { await StripeTerminal.discoverReaders({ type: readerType, - locationId: [TerminalConnectTypes.Usb].includes(readerType) + locationId: [TerminalConnectTypes.Usb, TerminalConnectTypes.Bluetooth].includes(readerType) ? 'tml_Ff37mAmk1XdBYT' // Auckland, New Zealand : 'tml_FOUOdQVIxvVdvN', // San Francisco, CA 94110 }).catch((e) => { diff --git a/demo/angular/tsconfig.json b/demo/angular/tsconfig.json index 7c07208a..248410a7 100644 --- a/demo/angular/tsconfig.json +++ b/demo/angular/tsconfig.json @@ -14,13 +14,13 @@ "sourceMap": true, "declaration": false, "experimentalDecorators": true, - "moduleResolution": "node", + "moduleResolution": "bundler", "importHelpers": true, "target": "es2022", "module": "es2022", "lib": [ "es2022", - "dom", + "dom" ], "useDefineForClassFields": false }, diff --git a/package.json b/package.json index 1170235e..c89d8754 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor-community/stripe", - "version": "7.1.2", + "version": "7.2.2", "engines": { "node": ">=20.0.0" }, @@ -15,7 +15,7 @@ "url": "git+https://github.com/simPRO-Software/stripe.git" }, "devDependencies": { - "np": "^7.7.0", + "np": "^10.2.0", "write-pkg": "^6.0.0" } } diff --git a/packages/identity/CapacitorCommunityStripeIdentity.podspec b/packages/identity/CapacitorCommunityStripeIdentity.podspec index ba5ffe06..bdad91b9 100644 --- a/packages/identity/CapacitorCommunityStripeIdentity.podspec +++ b/packages/identity/CapacitorCommunityStripeIdentity.podspec @@ -13,6 +13,6 @@ Pod::Spec.new do |s| s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' s.ios.deployment_target = '14.0' s.dependency 'Capacitor' - s.dependency 'StripeIdentity', '~> 24.12.1' + s.dependency 'StripeIdentity', '~> 25.1.0' s.swift_version = '5.1' end diff --git a/packages/identity/Package.swift b/packages/identity/Package.swift index e01b9085..834fc156 100644 --- a/packages/identity/Package.swift +++ b/packages/identity/Package.swift @@ -11,7 +11,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0"), - .package(url: "https://github.com/stripe/stripe-ios-spm.git", exact: "24.12.1") + .package(url: "https://github.com/stripe/stripe-ios-spm.git", exact: "25.1.0") ], targets: [ .target( diff --git a/packages/identity/README.md b/packages/identity/README.md index 8d48a729..99d760e9 100644 --- a/packages/identity/README.md +++ b/packages/identity/README.md @@ -33,18 +33,22 @@ If you want to implement, we recommend to read https://stripe.com/docs/identity ```ts import { StripeIdentity } from '@capacitor-community/stripe-identity'; +const listener = await StripeIdentity.addListener(IdentityVerificationSheetEventsEnum.VerificationResult, (result) => { + console.log(result); + listener.remove(); +}); + // initialize is needed only for Web Platform await StripeIdentity.initialize({ publishableKey, }); - await StripeIdentity.create({ ephemeralKeySecret, verificationId, // clientSecret is needed only for Web Platform clientSecret }); -const result = await StripeIdentity.present(); +await StripeIdentity.present(); ``` ## API @@ -56,6 +60,7 @@ const result = await StripeIdentity.present(); * [`present()`](#present) * [`addListener(IdentityVerificationSheetEventsEnum.Loaded, ...)`](#addlisteneridentityverificationsheeteventsenumloaded-) * [`addListener(IdentityVerificationSheetEventsEnum.FailedToLoad, ...)`](#addlisteneridentityverificationsheeteventsenumfailedtoload-) +* [`addListener(IdentityVerificationSheetEventsEnum.VerificationResult, ...)`](#addlisteneridentityverificationsheeteventsenumverificationresult-) * [`addListener(IdentityVerificationSheetEventsEnum.Completed, ...)`](#addlisteneridentityverificationsheeteventsenumcompleted-) * [`addListener(IdentityVerificationSheetEventsEnum.Canceled, ...)`](#addlisteneridentityverificationsheeteventsenumcanceled-) * [`addListener(IdentityVerificationSheetEventsEnum.Failed, ...)`](#addlisteneridentityverificationsheeteventsenumfailed-) @@ -97,10 +102,10 @@ create(options: CreateIdentityVerificationSheetOption) => Promise ### present() ```typescript -present() => Promise<{ identityVerificationResult: IdentityVerificationSheetResultInterface; }> +present() => Promise ``` -**Returns:** Promise<{ identityVerificationResult: IdentityVerificationSheetResultInterface; }> +**Returns:** Promise<deprecatedIdentityVerificationResult> -------------------- @@ -137,6 +142,22 @@ addListener(eventName: IdentityVerificationSheetEventsEnum.FailedToLoad, listene -------------------- +### addListener(IdentityVerificationSheetEventsEnum.VerificationResult, ...) + +```typescript +addListener(eventName: IdentityVerificationSheetEventsEnum.VerificationResult, listenerFunc: (result: IdentityVerificationResult) => void) => Promise +``` + +| Param | Type | +| ------------------ | ---------------------------------------------------------------------------------------------------------------------- | +| **`eventName`** | IdentityVerificationSheetEventsEnum.VerificationResult | +| **`listenerFunc`** | (result: IdentityVerificationResult) => void | + +**Returns:** Promise<PluginListenerHandle> + +-------------------- + + ### addListener(IdentityVerificationSheetEventsEnum.Completed, ...) ```typescript @@ -204,6 +225,13 @@ addListener(eventName: IdentityVerificationSheetEventsEnum.Failed, listenerFunc: | **`clientSecret`** | string | This client secret is used only for the web platform. | +#### deprecatedIdentityVerificationResult + +| Prop | Type | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------- | +| **`identityVerificationResult`** | IdentityVerificationSheetResultInterface | + + #### PluginListenerHandle | Prop | Type | @@ -215,9 +243,18 @@ addListener(eventName: IdentityVerificationSheetEventsEnum.Failed, listenerFunc: | Prop | Type | | ------------- | ------------------- | +| **`code`** | string | | **`message`** | string | +#### IdentityVerificationResult + +| Prop | Type | +| ------------ | ------------------------------------------------------------------------------------------------------------- | +| **`result`** | IdentityVerificationSheetResultInterface | +| **`error`** | StripeIdentityError | + + ### Type Aliases @@ -231,12 +268,13 @@ addListener(eventName: IdentityVerificationSheetEventsEnum.Failed, listenerFunc: #### IdentityVerificationSheetEventsEnum -| Members | Value | -| ------------------ | ---------------------------------------------------- | -| **`Loaded`** | 'identityVerificationSheetLoaded' | -| **`FailedToLoad`** | 'identityVerificationSheetFailedToLoad' | -| **`Completed`** | 'identityVerificationSheetCompleted' | -| **`Canceled`** | 'identityVerificationSheetCanceled' | -| **`Failed`** | 'identityVerificationSheetFailed' | +| Members | Value | +| ------------------------ | ---------------------------------------------------- | +| **`Loaded`** | 'identityVerificationSheetLoaded' | +| **`FailedToLoad`** | 'identityVerificationSheetFailedToLoad' | +| **`Completed`** | 'identityVerificationSheetCompleted' | +| **`Canceled`** | 'identityVerificationSheetCanceled' | +| **`Failed`** | 'identityVerificationSheetFailed' | +| **`VerificationResult`** | 'identityVerificationResult' | diff --git a/packages/identity/android/build.gradle b/packages/identity/android/build.gradle index 8ee3e352..7ea84bd9 100644 --- a/packages/identity/android/build.gradle +++ b/packages/identity/android/build.gradle @@ -5,7 +5,7 @@ ext { androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' playServicesWalletVersion = project.hasProperty('playServicesWalletVersion') ? rootProject.ext.playServicesWalletVersion : '19.2.+' - identityVersion = project.hasProperty('identityVersion') ? rootProject.ext.identityVersion : '21.12.+' + identityVersion = project.hasProperty('identityVersion') ? rootProject.ext.identityVersion : '22.2.+' } buildscript { diff --git a/packages/identity/android/src/main/java/com/getcapacitor/community/stripe/identity/IdentityVerificationSheetEvent.kt b/packages/identity/android/src/main/java/com/getcapacitor/community/stripe/identity/IdentityVerificationSheetEvent.kt index 16d00412..7c8a9516 100644 --- a/packages/identity/android/src/main/java/com/getcapacitor/community/stripe/identity/IdentityVerificationSheetEvent.kt +++ b/packages/identity/android/src/main/java/com/getcapacitor/community/stripe/identity/IdentityVerificationSheetEvent.kt @@ -7,4 +7,5 @@ enum class IdentityVerificationSheetEvent(val webEventName: String) { Completed("identityVerificationSheetCompleted"), Canceled("identityVerificationSheetCanceled"), Failed("identityVerificationSheetFailed"), + VerificationResult("identityVerificationResult"), } diff --git a/packages/identity/android/src/main/java/com/getcapacitor/community/stripe/identity/StripeIdentity.kt b/packages/identity/android/src/main/java/com/getcapacitor/community/stripe/identity/StripeIdentity.kt index 888058bd..9139aa0a 100644 --- a/packages/identity/android/src/main/java/com/getcapacitor/community/stripe/identity/StripeIdentity.kt +++ b/packages/identity/android/src/main/java/com/getcapacitor/community/stripe/identity/StripeIdentity.kt @@ -69,35 +69,70 @@ class StripeIdentity( } fun onVerificationCompleted(bridge: Bridge, callbackId: String?) { - val call = bridge.getSavedCall(callbackId) - notifyListeners(IdentityVerificationSheetEvent.Completed.webEventName, emptyObject) - call.resolve( + notifyListeners(IdentityVerificationSheetEvent.VerificationResult.webEventName, JSObject().put( - "identityVerificationResult", + "result", IdentityVerificationSheetEvent.Completed.webEventName ) ) + + val call = bridge.getSavedCall(callbackId) + notifyListeners(IdentityVerificationSheetEvent.Completed.webEventName, emptyObject) + if (call !== null) { + call.resolve( + JSObject().put( + "identityVerificationResult", + IdentityVerificationSheetEvent.Completed.webEventName + ) + ) + bridge.releaseCall(callbackId) + } } fun onVerificationCancelled(bridge: Bridge, callbackId: String?) { - val call = bridge.getSavedCall(callbackId) - notifyListeners(IdentityVerificationSheetEvent.Canceled.webEventName, emptyObject) - call.resolve( + notifyListeners(IdentityVerificationSheetEvent.VerificationResult.webEventName, JSObject().put( - "identityVerificationResult", + "result", IdentityVerificationSheetEvent.Canceled.webEventName ) ) + + val call = bridge.getSavedCall(callbackId) + notifyListeners(IdentityVerificationSheetEvent.Canceled.webEventName, emptyObject) + if (call !== null) { + call.resolve( + JSObject().put( + "identityVerificationResult", + IdentityVerificationSheetEvent.Canceled.webEventName + ) + ) + bridge.releaseCall(callbackId) + } } - fun onVerificationFailed(bridge: Bridge, callbackId: String?) { + fun onVerificationFailed(bridge: Bridge, errorMessage: String?, callbackId: String?) { + notifyListeners(IdentityVerificationSheetEvent.VerificationResult.webEventName, + JSObject() + .put( + "result", + IdentityVerificationSheetEvent.Failed.webEventName + ) + .put( + "error", + JSObject().put("message", errorMessage) + ) + ) + val call = bridge.getSavedCall(callbackId) notifyListeners(IdentityVerificationSheetEvent.Failed.webEventName, emptyObject) - call.resolve( - JSObject().put( - "identityVerificationResult", - IdentityVerificationSheetEvent.Failed.webEventName + if (call !== null) { + call.resolve( + JSObject().put( + "identityVerificationResult", + IdentityVerificationSheetEvent.Failed.webEventName + ) ) - ) + bridge.releaseCall(callbackId) + } } } diff --git a/packages/identity/android/src/main/java/com/getcapacitor/community/stripe/identity/StripeIdentityPlugin.kt b/packages/identity/android/src/main/java/com/getcapacitor/community/stripe/identity/StripeIdentityPlugin.kt index 8b70c9aa..9a2cc06c 100644 --- a/packages/identity/android/src/main/java/com/getcapacitor/community/stripe/identity/StripeIdentityPlugin.kt +++ b/packages/identity/android/src/main/java/com/getcapacitor/community/stripe/identity/StripeIdentityPlugin.kt @@ -48,7 +48,8 @@ class StripeIdentityPlugin : Plugin() { } else if (verificationFlowResult is VerificationFlowResult.Failed) { // If the flow fails, you should display the localized error // message to your user using throwable.getLocalizedMessage() - implementation.onVerificationFailed(bridge, identityVerificationCallbackId) + val errorMessage = verificationFlowResult.throwable.localizedMessage; + implementation.onVerificationFailed(bridge, errorMessage, identityVerificationCallbackId) } } } @@ -66,6 +67,8 @@ class StripeIdentityPlugin : Plugin() { @PluginMethod fun present(call: PluginCall) { identityVerificationCallbackId = call.callbackId + + call.setKeepAlive(true); bridge.saveCall(call) implementation.present(call) diff --git a/packages/identity/ios/Sources/StripeIdentityPlugin/IdentityVerificationSheetEvents.swift b/packages/identity/ios/Sources/StripeIdentityPlugin/IdentityVerificationSheetEvents.swift index 157550eb..c47eaa64 100644 --- a/packages/identity/ios/Sources/StripeIdentityPlugin/IdentityVerificationSheetEvents.swift +++ b/packages/identity/ios/Sources/StripeIdentityPlugin/IdentityVerificationSheetEvents.swift @@ -4,4 +4,5 @@ public enum IdentityVerificationSheetEvents: String { case Completed = "identityVerificationSheetCompleted" case Canceled = "identityVerificationSheetCanceled" case Failed = "identityVerificationSheetFailed" + case VerificationResult = "identityVerificationResult" } diff --git a/packages/identity/ios/Sources/StripeIdentityPlugin/StripeIdentity.swift b/packages/identity/ios/Sources/StripeIdentityPlugin/StripeIdentity.swift index a3726503..3f9cdb07 100644 --- a/packages/identity/ios/Sources/StripeIdentityPlugin/StripeIdentity.swift +++ b/packages/identity/ios/Sources/StripeIdentityPlugin/StripeIdentity.swift @@ -54,12 +54,20 @@ import StripeIdentity // The user has completed uploading their documents. // Let them know that the verification is processing. print("Verification Flow Completed!") + self.plugin?.notifyListeners(IdentityVerificationSheetEvents.VerificationResult.rawValue, data: [ + "result": IdentityVerificationSheetEvents.Completed.rawValue + ]) + self.plugin?.notifyListeners(IdentityVerificationSheetEvents.Completed.rawValue, data: [:]) call.resolve(["identityVerificationResult": IdentityVerificationSheetEvents.Completed.rawValue]) case .flowCanceled: // The user did not complete uploading their documents. // You should allow them to try again. print("Verification Flow Canceled!") + self.plugin?.notifyListeners(IdentityVerificationSheetEvents.VerificationResult.rawValue, data: [ + "result": IdentityVerificationSheetEvents.Canceled.rawValue + ]) + self.plugin?.notifyListeners(IdentityVerificationSheetEvents.Canceled.rawValue, data: [:]) call.resolve(["identityVerificationResult": IdentityVerificationSheetEvents.Canceled.rawValue]) case .flowFailed(let error): @@ -67,6 +75,13 @@ import StripeIdentity // message to your user using error.localizedDescription print("Verification Flow Failed!") print(error.localizedDescription) + self.plugin?.notifyListeners(IdentityVerificationSheetEvents.VerificationResult.rawValue, data: [ + "result": IdentityVerificationSheetEvents.Failed.rawValue, + "error": [ + "message": error.localizedDescription + ] + ]) + self.plugin?.notifyListeners(IdentityVerificationSheetEvents.Failed.rawValue, data: ["message": error.localizedDescription]) call.resolve(["identityVerificationResult": IdentityVerificationSheetEvents.Failed.rawValue]) } diff --git a/packages/identity/package-lock.json b/packages/identity/package-lock.json index aa109a96..38d93de7 100644 --- a/packages/identity/package-lock.json +++ b/packages/identity/package-lock.json @@ -1,12 +1,12 @@ { "name": "@capacitor-community/stripe-identity", - "version": "7.1.2", + "version": "7.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@capacitor-community/stripe-identity", - "version": "7.1.2", + "version": "7.2.2", "license": "MIT", "dependencies": { "@stripe/stripe-js": "^2.1.11" diff --git a/packages/identity/package.json b/packages/identity/package.json index e2e89726..7e7aed81 100644 --- a/packages/identity/package.json +++ b/packages/identity/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor-community/stripe-identity", - "version": "6.2.1", + "version": "7.2.2", "engines": { "node": ">=20.0.0" }, diff --git a/packages/identity/src/definitions.ts b/packages/identity/src/definitions.ts index 63445201..11507486 100644 --- a/packages/identity/src/definitions.ts +++ b/packages/identity/src/definitions.ts @@ -5,14 +5,34 @@ import type { CreateIdentityVerificationSheetOption, InitializeIdentityVerificat export * from './events.enum'; export interface StripeIdentityError { + code?: string; message: string; } + +/** + * @deprecated + * `identityVerificationResult` is deprecated and will be removed in the next major version (v8). + * + * + * This property relies on a PluginCall that cannot be persisted reliably across Android lifecycle events. + * Due to limitations in the Capacitor plugin system, the saved call is lost when the activity is destroyed + * (e.g., due to backgrounding, rotation, or process death). + * To avoid inconsistent behavior and potential crashes, please migrate to a stateless design where any necessary data + * is persisted explicitly and reprocessed on app resume or reload. + */ +export interface deprecatedIdentityVerificationResult { + identityVerificationResult: IdentityVerificationSheetResultInterface; +} + +export interface IdentityVerificationResult { + result: IdentityVerificationSheetResultInterface; + error?: StripeIdentityError; +} + export interface StripeIdentityPlugin { initialize(options: InitializeIdentityVerificationSheetOption): Promise; create(options: CreateIdentityVerificationSheetOption): Promise; - present(): Promise<{ - identityVerificationResult: IdentityVerificationSheetResultInterface; - }>; + present(): Promise; addListener( eventName: IdentityVerificationSheetEventsEnum.Loaded, @@ -24,16 +44,45 @@ export interface StripeIdentityPlugin { listenerFunc: (info: StripeIdentityError) => void, ): Promise; + addListener( + eventName: IdentityVerificationSheetEventsEnum.VerificationResult, + listenerFunc: (result: IdentityVerificationResult) => void, + ): Promise; + + /** + * @deprecated + * Listening to verification results via `present()` is deprecated. + * + * Please use `addListener(IdentityVerificationSheetEventsEnum.VerificationResult, listener)` instead. + * This new event-based approach provides better reliability across app lifecycle events such as backgrounding, + * rotation, and process restarts. + */ addListener( eventName: IdentityVerificationSheetEventsEnum.Completed, listenerFunc: () => void, ): Promise; + /** + * @deprecated + * Listening to verification results via `present()` is deprecated. + * + * Please use `addListener(IdentityVerificationSheetEventsEnum.VerificationResult, listener)` instead. + * This new event-based approach provides better reliability across app lifecycle events such as backgrounding, + * rotation, and process restarts. + */ addListener( eventName: IdentityVerificationSheetEventsEnum.Canceled, listenerFunc: () => void, ): Promise; + /** + * @deprecated + * Listening to verification results via `present()` is deprecated. + * + * Please use `addListener(IdentityVerificationSheetEventsEnum.VerificationResult, listener)` instead. + * This new event-based approach provides better reliability across app lifecycle events such as backgrounding, + * rotation, and process restarts. + */ addListener( eventName: IdentityVerificationSheetEventsEnum.Failed, listenerFunc: (info: StripeIdentityError) => void, diff --git a/packages/identity/src/events.enum.ts b/packages/identity/src/events.enum.ts index e14da92f..7d8cc73f 100644 --- a/packages/identity/src/events.enum.ts +++ b/packages/identity/src/events.enum.ts @@ -4,6 +4,7 @@ export enum IdentityVerificationSheetEventsEnum { Completed = 'identityVerificationSheetCompleted', Canceled = 'identityVerificationSheetCanceled', Failed = 'identityVerificationSheetFailed', + VerificationResult = 'identityVerificationResult' } export type IdentityVerificationSheetResultInterface = diff --git a/packages/identity/src/web.ts b/packages/identity/src/web.ts index 3564d586..ed22ee37 100644 --- a/packages/identity/src/web.ts +++ b/packages/identity/src/web.ts @@ -2,9 +2,8 @@ import { WebPlugin } from '@capacitor/core'; import type { Stripe } from '@stripe/stripe-js'; import { loadStripe } from '@stripe/stripe-js'; -import type { StripeIdentityPlugin } from './definitions'; +import type { deprecatedIdentityVerificationResult, StripeIdentityPlugin } from './definitions'; import { IdentityVerificationSheetEventsEnum } from './definitions'; -import type { IdentityVerificationSheetResultInterface } from './events.enum'; export interface InitializeIdentityVerificationSheetOption { publishableKey: string; @@ -30,9 +29,7 @@ export class StripeIdentityWeb extends WebPlugin implements StripeIdentityPlugin this.clientSecret = options.clientSecret; this.notifyListeners(IdentityVerificationSheetEventsEnum.Loaded, null); } - async present(): Promise<{ - identityVerificationResult: IdentityVerificationSheetResultInterface; - }> { + async present(): Promise { if (!this.stripe) { throw new Error('Stripe is not initialized.'); } @@ -46,18 +43,29 @@ export class StripeIdentityWeb extends WebPlugin implements StripeIdentityPlugin this.notifyListeners(IdentityVerificationSheetEventsEnum.Canceled, { message, }); + + this.notifyListeners(IdentityVerificationSheetEventsEnum.VerificationResult, { + result: IdentityVerificationSheetEventsEnum.Canceled, + }); return { identityVerificationResult: IdentityVerificationSheetEventsEnum.Canceled, }; } - this.notifyListeners(IdentityVerificationSheetEventsEnum.Failed, { - message, + this.notifyListeners(IdentityVerificationSheetEventsEnum.Failed, error); + + this.notifyListeners(IdentityVerificationSheetEventsEnum.VerificationResult, { + result: IdentityVerificationSheetEventsEnum.Failed, + error, }); return { identityVerificationResult: IdentityVerificationSheetEventsEnum.Failed, }; } this.notifyListeners(IdentityVerificationSheetEventsEnum.Completed, null); + + this.notifyListeners(IdentityVerificationSheetEventsEnum.VerificationResult, { + result: IdentityVerificationSheetEventsEnum.Completed, + }); return { identityVerificationResult: IdentityVerificationSheetEventsEnum.Completed, }; diff --git a/packages/payment/CapacitorCommunityStripe.podspec b/packages/payment/CapacitorCommunityStripe.podspec index 6b71e5fc..ee9efc66 100644 --- a/packages/payment/CapacitorCommunityStripe.podspec +++ b/packages/payment/CapacitorCommunityStripe.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' s.ios.deployment_target = '14.0' s.dependency 'Capacitor' - s.dependency 'StripePaymentSheet', '~> 24.12.1' - s.dependency 'StripeApplePay', '~> 24.12.1' + s.dependency 'StripePaymentSheet', '~> 25.1.0' + s.dependency 'StripeApplePay', '~> 25.1.0' s.swift_version = '5.1' end diff --git a/packages/payment/Package.swift b/packages/payment/Package.swift index 004a1664..65db86a2 100644 --- a/packages/payment/Package.swift +++ b/packages/payment/Package.swift @@ -11,7 +11,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0"), - .package(url: "https://github.com/stripe/stripe-ios-spm.git", exact: "24.12.1") + .package(url: "https://github.com/stripe/stripe-ios-spm.git", exact: "25.1.0") ], targets: [ .target( diff --git a/packages/payment/README.md b/packages/payment/README.md index b76c6b19..7fe79936 100644 --- a/packages/payment/README.md +++ b/packages/payment/README.md @@ -683,23 +683,58 @@ addListener(eventName: PaymentSheetEventsEnum.Failed, listenerFunc: (error: stri #### CreatePaymentFlowOption -| Prop | Type | Description | Default | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------- | -| **`paymentIntentClientSecret`** | string | Any documentation call 'paymentIntent' Set paymentIntentClientSecret or setupIntentClientSecret | | -| **`setupIntentClientSecret`** | string | Any documentation call 'paymentIntent' Set paymentIntentClientSecret or setupIntentClientSecret | | -| **`billingDetailsCollectionConfiguration`** | BillingDetailsCollectionConfiguration | Optional billingDetailsCollectionConfiguration | | -| **`customerEphemeralKeySecret`** | string | Any documentation call 'ephemeralKey' | | -| **`customerId`** | string | Any documentation call 'customer' | | -| **`enableApplePay`** | boolean | If you set payment method ApplePay, this set true | false | -| **`applePayMerchantId`** | string | If set enableApplePay false, Plugin ignore here. | | -| **`enableGooglePay`** | boolean | If you set payment method GooglePay, this set true | false | -| **`GooglePayIsTesting`** | boolean | | false, | -| **`countryCode`** | string | use ApplePay and GooglePay. If set enableApplePay and enableGooglePay false, Plugin ignore here. | "US" | -| **`merchantDisplayName`** | string | | "App Name" | -| **`returnURL`** | string | | "" | -| **`style`** | 'alwaysLight' \| 'alwaysDark' | iOS Only | undefined | -| **`withZipCode`** | boolean | Platform: Web only Show ZIP code field. | true | -| **`currencyCode`** | string | use GooglePay. Required if enableGooglePay is true for setupIntents. | "USD" | +| Prop | Type | Description | Default | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| **`paymentIntentClientSecret`** | string | Any documentation call 'paymentIntent' Set paymentIntentClientSecret or setupIntentClientSecret | | +| **`setupIntentClientSecret`** | string | Any documentation call 'paymentIntent' Set paymentIntentClientSecret or setupIntentClientSecret | | +| **`defaultBillingDetails`** | DefaultBillingDetails | Optional defaultBillingDetails This is ios/android only. not support web. https://docs.stripe.com/payments/mobile/collect-addresses?payment-ui=mobile&platform=ios#set-default-billing-details | | +| **`shippingDetails`** | AddressDetails | Optional shippingDetails This is android only. ios requires an address element. https://docs.stripe.com/payments/mobile/collect-addresses?payment-ui=mobile&platform=android#prefill-addresses | | +| **`billingDetailsCollectionConfiguration`** | BillingDetailsCollectionConfiguration | Optional billingDetailsCollectionConfiguration This is ios/android only. not support web. https://docs.stripe.com/payments/mobile/collect-addresses?payment-ui=mobile&platform=ios#customize-billing-details-collection | | +| **`customerEphemeralKeySecret`** | string | Any documentation call 'ephemeralKey' | | +| **`customerId`** | string | Any documentation call 'customer' | | +| **`enableApplePay`** | boolean | If you set payment method ApplePay, this set true | false | +| **`applePayMerchantId`** | string | If set enableApplePay false, Plugin ignore here. | | +| **`enableGooglePay`** | boolean | If you set payment method GooglePay, this set true | false | +| **`GooglePayIsTesting`** | boolean | | false, | +| **`countryCode`** | string | use ApplePay and GooglePay. If set enableApplePay and enableGooglePay false, Plugin ignore here. | "US" | +| **`merchantDisplayName`** | string | | "App Name" | +| **`returnURL`** | string | | "" | +| **`paymentMethodLayout`** | 'automatic' \| 'horizontal' \| 'vertical' | | "automatic" | +| **`style`** | 'alwaysLight' \| 'alwaysDark' | iOS Only | undefined | +| **`withZipCode`** | boolean | Platform: Web only Show ZIP code field. | true | +| **`currencyCode`** | string | use GooglePay. Required if enableGooglePay is true for setupIntents. | "USD" | + + +#### DefaultBillingDetails + +| Prop | Type | +| ------------- | ------------------------------------------- | +| **`email`** | string | +| **`name`** | string | +| **`phone`** | string | +| **`address`** | Address | + + +#### Address + +| Prop | Type | Description | +| ---------------- | ------------------- | --------------------------------------------- | +| **`country`** | string | Two-letter country code (ISO 3166-1 alpha-2). | +| **`city`** | string | | +| **`line1`** | string | | +| **`line2`** | string | | +| **`postalCode`** | string | | +| **`state`** | string | | + + +#### AddressDetails + +| Prop | Type | +| ------------------------ | ------------------------------------------- | +| **`name`** | string | +| **`address`** | Address | +| **`phone`** | string | +| **`isCheckboxSelected`** | boolean | #### BillingDetailsCollectionConfiguration @@ -714,23 +749,26 @@ addListener(eventName: PaymentSheetEventsEnum.Failed, listenerFunc: (error: stri #### CreatePaymentSheetOption -| Prop | Type | Description | Default | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------- | -| **`paymentIntentClientSecret`** | string | Any documentation call 'paymentIntent' Set paymentIntentClientSecret or setupIntentClientSecret | | -| **`setupIntentClientSecret`** | string | Any documentation call 'paymentIntent' Set paymentIntentClientSecret or setupIntentClientSecret | | -| **`billingDetailsCollectionConfiguration`** | BillingDetailsCollectionConfiguration | Optional billingDetailsCollectionConfiguration | | -| **`customerEphemeralKeySecret`** | string | Any documentation call 'ephemeralKey' | | -| **`customerId`** | string | Any documentation call 'customer' | | -| **`enableApplePay`** | boolean | If you set payment method ApplePay, this set true | false | -| **`applePayMerchantId`** | string | If set enableApplePay false, Plugin ignore here. | | -| **`enableGooglePay`** | boolean | If you set payment method GooglePay, this set true | false | -| **`GooglePayIsTesting`** | boolean | | false, | -| **`countryCode`** | string | use ApplePay and GooglePay. If set enableApplePay and enableGooglePay false, Plugin ignore here. | "US" | -| **`merchantDisplayName`** | string | | "App Name" | -| **`returnURL`** | string | | "" | -| **`style`** | 'alwaysLight' \| 'alwaysDark' | iOS Only | undefined | -| **`withZipCode`** | boolean | Platform: Web only Show ZIP code field. | true | -| **`currencyCode`** | string | use GooglePay. Required if enableGooglePay is true for setupIntents. | "USD" | +| Prop | Type | Description | Default | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| **`paymentIntentClientSecret`** | string | Any documentation call 'paymentIntent' Set paymentIntentClientSecret or setupIntentClientSecret | | +| **`setupIntentClientSecret`** | string | Any documentation call 'paymentIntent' Set paymentIntentClientSecret or setupIntentClientSecret | | +| **`defaultBillingDetails`** | DefaultBillingDetails | Optional defaultBillingDetails This is ios/android only. not support web. https://docs.stripe.com/payments/mobile/collect-addresses?payment-ui=mobile&platform=ios#set-default-billing-details | | +| **`shippingDetails`** | AddressDetails | Optional shippingDetails This is android only. ios requires an address element. https://docs.stripe.com/payments/mobile/collect-addresses?payment-ui=mobile&platform=android#prefill-addresses | | +| **`billingDetailsCollectionConfiguration`** | BillingDetailsCollectionConfiguration | Optional billingDetailsCollectionConfiguration This is ios/android only. not support web. https://docs.stripe.com/payments/mobile/collect-addresses?payment-ui=mobile&platform=ios#customize-billing-details-collection | | +| **`customerEphemeralKeySecret`** | string | Any documentation call 'ephemeralKey' | | +| **`customerId`** | string | Any documentation call 'customer' | | +| **`enableApplePay`** | boolean | If you set payment method ApplePay, this set true | false | +| **`applePayMerchantId`** | string | If set enableApplePay false, Plugin ignore here. | | +| **`enableGooglePay`** | boolean | If you set payment method GooglePay, this set true | false | +| **`GooglePayIsTesting`** | boolean | | false, | +| **`countryCode`** | string | use ApplePay and GooglePay. If set enableApplePay and enableGooglePay false, Plugin ignore here. | "US" | +| **`merchantDisplayName`** | string | | "App Name" | +| **`returnURL`** | string | | "" | +| **`paymentMethodLayout`** | 'automatic' \| 'horizontal' \| 'vertical' | | "automatic" | +| **`style`** | 'alwaysLight' \| 'alwaysDark' | iOS Only | undefined | +| **`withZipCode`** | boolean | Platform: Web only Show ZIP code field. | true | +| **`currencyCode`** | string | use GooglePay. Required if enableGooglePay is true for setupIntents. | "USD" | ### Type Aliases diff --git a/packages/payment/android/build.gradle b/packages/payment/android/build.gradle index bc0d02f4..158f1a33 100644 --- a/packages/payment/android/build.gradle +++ b/packages/payment/android/build.gradle @@ -5,7 +5,7 @@ ext { androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' playServicesWalletVersion = project.hasProperty('playServicesWalletVersion') ? rootProject.ext.playServicesWalletVersion : '19.2.+' - stripeAndroidVersion = project.hasProperty('stripeAndroidVersion') ? rootProject.ext.stripeAndroidVersion : '21.12.+' + stripeAndroidVersion = project.hasProperty('stripeAndroidVersion') ? rootProject.ext.stripeAndroidVersion : '22.2.+' gsonVersion = project.hasProperty('gsonVersion') ? rootProject.ext.gsonVersion : '2.10.+' } diff --git a/packages/payment/android/src/main/java/com/getcapacitor/community/stripe/helper/PaymentSheetHelper.kt b/packages/payment/android/src/main/java/com/getcapacitor/community/stripe/helper/PaymentSheetHelper.kt new file mode 100644 index 00000000..15296b8a --- /dev/null +++ b/packages/payment/android/src/main/java/com/getcapacitor/community/stripe/helper/PaymentSheetHelper.kt @@ -0,0 +1,73 @@ +package com.getcapacitor.community.stripe.helper + +import com.getcapacitor.JSObject +import com.stripe.android.paymentsheet.PaymentSheet +import com.stripe.android.paymentsheet.addresselement.AddressDetails +import com.stripe.android.paymentsheet.PaymentSheet.BillingDetailsCollectionConfiguration +import com.stripe.android.paymentsheet.PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode +import com.stripe.android.paymentsheet.PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode + +class PaymentSheetHelper { + fun fromJSObjectToBillingDetails(obj: JSObject?): PaymentSheet.BillingDetails { + if (obj == null) return PaymentSheet.BillingDetails() + val address = fromJSObjectToAddress(obj.getJSObject("address", null)) + return PaymentSheet.BillingDetails( + address = address, + email = obj.getString("email", null), + name = obj.getString("name", null), + phone = obj.getString("phone", null) + ) + } + + fun fromJSObjectToShippingDetails(obj: JSObject?): AddressDetails? { + if (obj == null) return null + val address = fromJSObjectToAddress(obj.getJSObject("address", null)) + return AddressDetails( + name = obj.getString("name", null), + address = address, + phoneNumber = obj.getString("phone", null), + isCheckboxSelected = obj.getBoolean("isCheckboxSelected", false) + ) + } + + fun fromJSObjectToBillingCollectionConfig(obj: JSObject?): BillingDetailsCollectionConfiguration { + if (obj == null) return BillingDetailsCollectionConfiguration() + + val name = parseCollectionMode(obj.getString("name")) + val phone = parseCollectionMode(obj.getString("phone")) + val email = parseCollectionMode(obj.getString("email")) + val address = parseAddressCollectionMode(obj.getString("address")) + + return BillingDetailsCollectionConfiguration( + name = name, + phone = phone, + email = email, + address = address, + attachDefaultsToPaymentMethod = false + ) + } + + private fun fromJSObjectToAddress(obj: JSObject?): PaymentSheet.Address { + if (obj == null) return PaymentSheet.Address() + return PaymentSheet.Address( + country = obj.getString("country", null), + city = obj.getString("city", null), + line1 = obj.getString("line1", null), + line2 = obj.getString("line2", null), + postalCode = obj.getString("postalCode", null), + state = obj.getString("state", null) + ) + } + + private fun parseCollectionMode(mode: String?): CollectionMode = when (mode) { + "always" -> CollectionMode.Always + "never" -> CollectionMode.Never + else -> CollectionMode.Automatic + } + + private fun parseAddressCollectionMode(mode: String?): AddressCollectionMode = when (mode) { + "full" -> AddressCollectionMode.Full + "never" -> AddressCollectionMode.Never + else -> AddressCollectionMode.Automatic + } +} \ No newline at end of file diff --git a/packages/payment/android/src/main/java/com/getcapacitor/community/stripe/paymentflow/PaymentFlowExecutor.kt b/packages/payment/android/src/main/java/com/getcapacitor/community/stripe/paymentflow/PaymentFlowExecutor.kt index 1cac8d6f..f5f222ba 100644 --- a/packages/payment/android/src/main/java/com/getcapacitor/community/stripe/paymentflow/PaymentFlowExecutor.kt +++ b/packages/payment/android/src/main/java/com/getcapacitor/community/stripe/paymentflow/PaymentFlowExecutor.kt @@ -6,9 +6,11 @@ import androidx.core.util.Supplier import com.getcapacitor.Bridge import com.getcapacitor.JSObject import com.getcapacitor.PluginCall +import com.getcapacitor.community.stripe.helper.PaymentSheetHelper import com.getcapacitor.community.stripe.models.Executor import com.google.android.gms.common.util.BiConsumer import com.stripe.android.paymentsheet.PaymentSheet +import com.stripe.android.paymentsheet.PaymentSheet.PaymentMethodLayout import com.stripe.android.paymentsheet.PaymentSheetResult import com.stripe.android.paymentsheet.model.PaymentOption @@ -25,8 +27,8 @@ class PaymentFlowExecutor( "PaymentFlowExecutor" ) { var flowController: PaymentSheet.FlowController? = null + private var configurationBuilder: PaymentSheet.Configuration.Builder? = null private val emptyObject = JSObject() - private var paymentConfiguration: PaymentSheet.Configuration? = null init { this.contextSupplier = contextSupplier @@ -38,6 +40,13 @@ class PaymentFlowExecutor( val customerEphemeralKeySecret = call.getString("customerEphemeralKeySecret", null) val customerId = call.getString("customerId", null) + val paymentMethodLayout: PaymentMethodLayout = when (call.getString("paymentMethodLayout", "automatic")) { + "horizontal" -> PaymentMethodLayout.Horizontal + "vertical" -> PaymentMethodLayout.Vertical + "automatic" -> PaymentMethodLayout.Automatic + else -> PaymentMethodLayout.Automatic + } + if (paymentIntentClientSecret == null && setupIntentClientSecret == null) { val errorText = "Invalid Params. This method require paymentIntentClientSecret or setupIntentClientSecret." @@ -72,32 +81,40 @@ class PaymentFlowExecutor( ) PaymentSheet.CustomerConfiguration(customerId, customerEphemeralKeySecret!!) else null - if (!enableGooglePay!!) { - paymentConfiguration = PaymentSheet.Configuration(merchantDisplayName, customer) - } else { - val GooglePayEnvironment = call.getBoolean("GooglePayIsTesting", false) + val defaultBillingDetailsConfiguration = PaymentSheetHelper().fromJSObjectToBillingDetails(call.getObject("defaultBillingDetails", null)) + val shippingDetailsConfiguration = PaymentSheetHelper().fromJSObjectToShippingDetails(call.getObject("shippingDetails", null)); + val billingDetailsCollectionConfiguration = PaymentSheetHelper().fromJSObjectToBillingCollectionConfig( + call.getObject("billingDetailsCollectionConfiguration", null) + ) + + configurationBuilder = PaymentSheet.Configuration.Builder(merchantDisplayName) + .customer(customer) + .defaultBillingDetails(defaultBillingDetailsConfiguration) + .shippingDetails(shippingDetailsConfiguration) + .billingDetailsCollectionConfiguration(billingDetailsCollectionConfiguration) + .paymentMethodLayout(paymentMethodLayout) + + if (enableGooglePay!!) { + val googlePayEnvironment = call.getBoolean("GooglePayIsTesting", false)!! var environment: PaymentSheet.GooglePayConfiguration.Environment = PaymentSheet.GooglePayConfiguration.Environment.Production - if (GooglePayEnvironment!!) { + if (googlePayEnvironment) { environment = PaymentSheet.GooglePayConfiguration.Environment.Test } - paymentConfiguration = PaymentSheet.Configuration( - merchantDisplayName, - customer, - PaymentSheet.GooglePayConfiguration( - environment, - call.getString("countryCode", "US")!! - ) - ) + configurationBuilder!!.googlePay(PaymentSheet.GooglePayConfiguration( + environment, + call.getString("countryCode", "US")!!, + call.getString("currencyCode", null) + )) } if (setupIntentClientSecret != null) { flowController!!.configureWithSetupIntent( setupIntentClientSecret, - paymentConfiguration + configurationBuilder!!.build() ) { success: Boolean, error: Throwable? -> if (success) { notifyListenersFunction.accept( @@ -116,7 +133,7 @@ class PaymentFlowExecutor( } else if (paymentIntentClientSecret != null) { flowController!!.configureWithPaymentIntent( paymentIntentClientSecret, - paymentConfiguration + configurationBuilder!!.build() ) { success: Boolean, error: Throwable? -> if (success) { notifyListenersFunction.accept( diff --git a/packages/payment/android/src/main/java/com/getcapacitor/community/stripe/paymentsheet/PaymentSheetExecutor.kt b/packages/payment/android/src/main/java/com/getcapacitor/community/stripe/paymentsheet/PaymentSheetExecutor.kt index af30e7b1..521776ce 100644 --- a/packages/payment/android/src/main/java/com/getcapacitor/community/stripe/paymentsheet/PaymentSheetExecutor.kt +++ b/packages/payment/android/src/main/java/com/getcapacitor/community/stripe/paymentsheet/PaymentSheetExecutor.kt @@ -6,11 +6,10 @@ import androidx.core.util.Supplier import com.getcapacitor.Bridge import com.getcapacitor.JSObject import com.getcapacitor.PluginCall +import com.getcapacitor.community.stripe.helper.PaymentSheetHelper import com.getcapacitor.community.stripe.models.Executor import com.google.android.gms.common.util.BiConsumer import com.stripe.android.paymentsheet.PaymentSheet -import com.stripe.android.paymentsheet.PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode -import com.stripe.android.paymentsheet.PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode import com.stripe.android.paymentsheet.PaymentSheetResult class PaymentSheetExecutor( @@ -26,8 +25,8 @@ class PaymentSheetExecutor( "PaymentSheetExecutor" ) { var paymentSheet: PaymentSheet? = null + private var configurationBuilder: PaymentSheet.Configuration.Builder? = null private val emptyObject = JSObject() - private var paymentConfiguration: PaymentSheet.Configuration? = null private var paymentIntentClientSecret: String? = null private var setupIntentClientSecret: String? = null @@ -43,6 +42,13 @@ class PaymentSheetExecutor( val customerEphemeralKeySecret = call.getString("customerEphemeralKeySecret", null) val customerId = call.getString("customerId", null) + val paymentMethodLayout: PaymentSheet.PaymentMethodLayout = when (call.getString("paymentMethodLayout", "automatic")) { + "horizontal" -> PaymentSheet.PaymentMethodLayout.Horizontal + "vertical" -> PaymentSheet.PaymentMethodLayout.Vertical + "automatic" -> PaymentSheet.PaymentMethodLayout.Automatic + else -> PaymentSheet.PaymentMethodLayout.Automatic + } + if (paymentIntentClientSecret == null && setupIntentClientSecret == null) { val errorText = "Invalid Params. This method require paymentIntentClientSecret or setupIntentClientSecret." @@ -77,94 +83,35 @@ class PaymentSheetExecutor( ) PaymentSheet.CustomerConfiguration(customerId, customerEphemeralKeySecret!!) else null - var billingDetailsCollectionConfiguration: PaymentSheet.BillingDetailsCollectionConfiguration? = null - val bdCollectionConfiguration = - call.getObject("billingDetailsCollectionConfiguration", null) - if (bdCollectionConfiguration != null) { - val emailCollectionMode = bdCollectionConfiguration.getString("email") - val nameCollectionMode = bdCollectionConfiguration.getString("name") - val phoneCollectionMode = bdCollectionConfiguration.getString("phone") - val addressCollectionMode = bdCollectionConfiguration.getString("address") - - val nameMode = when (nameCollectionMode) { - "always" -> CollectionMode.Always - "never" -> CollectionMode.Never - else -> CollectionMode.Automatic - } - - val phoneMode = when (phoneCollectionMode) { - "always" -> CollectionMode.Always - "never" -> CollectionMode.Never - else -> CollectionMode.Automatic - } - - val emailMode = when (emailCollectionMode) { - "always" -> CollectionMode.Always - "never" -> CollectionMode.Never - else -> CollectionMode.Automatic - } - val addressMode = when (addressCollectionMode) { - "full" -> AddressCollectionMode.Full - "never" -> AddressCollectionMode.Never - else -> AddressCollectionMode.Automatic - } + val defaultBillingDetailsConfiguration = PaymentSheetHelper().fromJSObjectToBillingDetails(call.getObject("defaultBillingDetails", null)) + val shippingDetailsConfiguration = PaymentSheetHelper().fromJSObjectToShippingDetails(call.getObject("shippingDetails", null)); + val billingDetailsCollectionConfiguration = PaymentSheetHelper().fromJSObjectToBillingCollectionConfig( + call.getObject("billingDetailsCollectionConfiguration", null) + ) - billingDetailsCollectionConfiguration = - PaymentSheet.BillingDetailsCollectionConfiguration( - nameMode, - phoneMode, - emailMode, - addressMode, - attachDefaultsToPaymentMethod = false - ) - } + configurationBuilder = PaymentSheet.Configuration.Builder(merchantDisplayName) + .customer(customer) + .defaultBillingDetails(defaultBillingDetailsConfiguration) + .shippingDetails(shippingDetailsConfiguration) + .billingDetailsCollectionConfiguration(billingDetailsCollectionConfiguration) + .paymentMethodLayout(paymentMethodLayout) - if (!enableGooglePay!!) { - paymentConfiguration = if (bdCollectionConfiguration != null) { - PaymentSheet.Configuration( - merchantDisplayName, - customer, - billingDetailsCollectionConfiguration = billingDetailsCollectionConfiguration!! - ) - } else { - PaymentSheet.Configuration( - merchantDisplayName, - customer, - ) - } - } else { - val googlePayEnvironment = call.getBoolean("GooglePayIsTesting", false) + if (enableGooglePay!!) { + val googlePayEnvironment = call.getBoolean("GooglePayIsTesting", false)!! var environment: PaymentSheet.GooglePayConfiguration.Environment = PaymentSheet.GooglePayConfiguration.Environment.Production - if (googlePayEnvironment!!) { + if (googlePayEnvironment) { environment = PaymentSheet.GooglePayConfiguration.Environment.Test } - paymentConfiguration = if (bdCollectionConfiguration != null) { - PaymentSheet.Configuration( - merchantDisplayName, - customer, - billingDetailsCollectionConfiguration = billingDetailsCollectionConfiguration!!, - googlePay = PaymentSheet.GooglePayConfiguration( - environment, - call.getString("countryCode", "US")!!, - call.getString("currencyCode", null) - ), - ) - } else { - PaymentSheet.Configuration( - merchantDisplayName, - customer, - googlePay = PaymentSheet.GooglePayConfiguration( - environment, - call.getString("countryCode", "US")!!, - call.getString("currencyCode", null) - ), - ) - } + configurationBuilder!!.googlePay(PaymentSheet.GooglePayConfiguration( + environment, + call.getString("countryCode", "US")!!, + call.getString("currencyCode", null) + )) } notifyListenersFunction.accept(PaymentSheetEvents.Loaded.webEventName, emptyObject) @@ -176,12 +123,12 @@ class PaymentSheetExecutor( if (paymentIntentClientSecret != null) { paymentSheet!!.presentWithPaymentIntent( paymentIntentClientSecret!!, - paymentConfiguration + configurationBuilder!!.build() ) } else { paymentSheet!!.presentWithSetupIntent( setupIntentClientSecret!!, - paymentConfiguration + configurationBuilder!!.build() ) } } catch (ex: Exception) { diff --git a/packages/payment/ios/Sources/StripePlugin/ApplePay/ApplePayExecutor.swift b/packages/payment/ios/Sources/StripePlugin/ApplePay/ApplePayExecutor.swift index 1db77a34..312e0e56 100644 --- a/packages/payment/ios/Sources/StripePlugin/ApplePay/ApplePayExecutor.swift +++ b/packages/payment/ios/Sources/StripePlugin/ApplePay/ApplePayExecutor.swift @@ -153,12 +153,12 @@ extension ApplePayExecutor { } } - func applePayContext(_ context: STPApplePayContext, didCreatePaymentMethod paymentMethod: StripeAPI.PaymentMethod, paymentInformation: PKPayment, completion: @escaping STPIntentClientSecretCompletionBlock) { - let clientSecret = self.appleClientSecret - let error: String? = nil // Call the completion block with the client secret or an error - completion(clientSecret, error as? Error) - let jsonArray = self.transformPKContactToJSON(contact: paymentInformation.shippingContact) - self.plugin?.notifyListeners(ApplePayEvents.DidCreatePaymentMethod.rawValue, data: ["contact": jsonArray]) + func applePayContext(_ context: STPApplePayContext, didCreatePaymentMethod paymentMethod: StripeAPI.PaymentMethod, paymentInformation: PKPayment) async throws -> String { + return try await withCheckedThrowingContinuation { continuation in + continuation.resume(with: .success(self.appleClientSecret)) + let jsonArray = self.transformPKContactToJSON(contact: paymentInformation.shippingContact) + self.plugin?.notifyListeners(ApplePayEvents.DidCreatePaymentMethod.rawValue, data: ["contact": jsonArray]) + } } func applePayContext(_ context: STPApplePayContext, didCompleteWith status: STPApplePayContext.PaymentStatus, error: Error?) { diff --git a/packages/payment/ios/Sources/StripePlugin/PaymentFlow/PaymentFlowExecutor.swift b/packages/payment/ios/Sources/StripePlugin/PaymentFlow/PaymentFlowExecutor.swift index 0df3609d..d316218b 100644 --- a/packages/payment/ios/Sources/StripePlugin/PaymentFlow/PaymentFlowExecutor.swift +++ b/packages/payment/ios/Sources/StripePlugin/PaymentFlow/PaymentFlowExecutor.swift @@ -30,6 +30,15 @@ class PaymentFlowExecutor: NSObject { // MARK: Create a PaymentSheet instance var configuration = PaymentSheet.Configuration() + let paymentMethodLayout = call.getString("paymentMethodLayout") ?? "automatic" + if paymentMethodLayout == "horizontal" { + configuration.paymentMethodLayout = .horizontal + } else if paymentMethodLayout == "vertical" { + configuration.paymentMethodLayout = .vertical + } else { + configuration.paymentMethodLayout = .automatic + } + let merchantDisplayName = call.getString("merchantDisplayName") ?? "" if merchantDisplayName != "" { configuration.merchantDisplayName = merchantDisplayName @@ -40,13 +49,11 @@ class PaymentFlowExecutor: NSObject { configuration.returnURL = returnURL } - if #available(iOS 13.0, *) { - let style = call.getString("style") ?? "" - if style == "alwaysLight" { - configuration.style = .alwaysLight - } else if style == "alwaysDark" { - configuration.style = .alwaysDark - } + let style = call.getString("style") ?? "" + if style == "alwaysLight" { + configuration.style = .alwaysLight + } else if style == "alwaysDark" { + configuration.style = .alwaysDark } let applePayMerchantId = call.getString("applePayMerchantId") ?? "" @@ -62,6 +69,59 @@ class PaymentFlowExecutor: NSObject { configuration.customer = .init(id: customerId!, ephemeralKeySecret: customerEphemeralKeySecret!) } + let billingDetailsCollectionConfiguration = call.getObject("billingDetailsCollectionConfiguration") ?? nil + if billingDetailsCollectionConfiguration != nil { + billingDetailsCollectionConfiguration?.forEach({ (key: String, value: JSValue) in + let val: String = value as? String ?? "automatic" + switch key { + case "email": + configuration.billingDetailsCollectionConfiguration.email = PaymentSheetHelper().getCollectionModeValue(mode: val) + case "name": + configuration.billingDetailsCollectionConfiguration.name = PaymentSheetHelper().getCollectionModeValue(mode: val) + case "phone": + configuration.billingDetailsCollectionConfiguration.phone = PaymentSheetHelper().getCollectionModeValue(mode: val) + case "address": + configuration.billingDetailsCollectionConfiguration.address = PaymentSheetHelper().getAddressCollectionModeValue(mode: val) + default: + return + } + }) + } + + let defaultBillingDetails = call.getObject("defaultBillingDetails") ?? nil + if defaultBillingDetails != nil { + defaultBillingDetails?.forEach({ (key: String, value: JSValue) in + switch key { + case "email": + if let val = value as? String { + configuration.defaultBillingDetails.email = val + } + case "name": + if let val = value as? String { + configuration.defaultBillingDetails.name = val + } + case "phone": + if let val = value as? String { + configuration.defaultBillingDetails.phone = val + } + case "address": + if let val = value as? JSObject { + let address = PaymentSheet.Address( + city: val["city"] as? String, + country: val["country"] as? String, + line1: val["line1"] as? String, + line2: val["line2"] as? String, + postalCode: val["postalCode"] as? String, + state: val["state"] as? String + ) + configuration.defaultBillingDetails.address = address + } + default: + return + } + }) + } + if setupIntentClientSecret != nil { PaymentSheet.FlowController.create(setupIntentClientSecret: setupIntentClientSecret!, configuration: configuration) { [weak self] result in diff --git a/packages/payment/ios/Sources/StripePlugin/PaymentSheet/PaymentSheetExecutor.swift b/packages/payment/ios/Sources/StripePlugin/PaymentSheet/PaymentSheetExecutor.swift index c0919d2a..8841db5b 100644 --- a/packages/payment/ios/Sources/StripePlugin/PaymentSheet/PaymentSheetExecutor.swift +++ b/packages/payment/ios/Sources/StripePlugin/PaymentSheet/PaymentSheetExecutor.swift @@ -30,6 +30,15 @@ class PaymentSheetExecutor: NSObject { // MARK: Create a PaymentSheet instance var configuration = PaymentSheet.Configuration() + let paymentMethodLayout = call.getString("paymentMethodLayout") ?? "automatic" + if paymentMethodLayout == "horizontal" { + configuration.paymentMethodLayout = .horizontal + } else if paymentMethodLayout == "vertical" { + configuration.paymentMethodLayout = .vertical + } else { + configuration.paymentMethodLayout = .automatic + } + let merchantDisplayName = call.getString("merchantDisplayName") ?? "" if merchantDisplayName != "" { configuration.merchantDisplayName = merchantDisplayName @@ -40,13 +49,11 @@ class PaymentSheetExecutor: NSObject { configuration.returnURL = returnURL } - if #available(iOS 13.0, *) { - let style = call.getString("style") ?? "" - if style == "alwaysLight" { - configuration.style = .alwaysLight - } else if style == "alwaysDark" { - configuration.style = .alwaysDark - } + let style = call.getString("style") ?? "" + if style == "alwaysLight" { + configuration.style = .alwaysLight + } else if style == "alwaysDark" { + configuration.style = .alwaysDark } let applePayMerchantId = call.getString("applePayMerchantId") ?? "" @@ -68,13 +75,47 @@ class PaymentSheetExecutor: NSObject { let val: String = value as? String ?? "automatic" switch key { case "email": - configuration.billingDetailsCollectionConfiguration.email = getCollectionModeValue(mode: val) + configuration.billingDetailsCollectionConfiguration.email = PaymentSheetHelper().getCollectionModeValue(mode: val) + case "name": + configuration.billingDetailsCollectionConfiguration.name = PaymentSheetHelper().getCollectionModeValue(mode: val) + case "phone": + configuration.billingDetailsCollectionConfiguration.phone = PaymentSheetHelper().getCollectionModeValue(mode: val) + case "address": + configuration.billingDetailsCollectionConfiguration.address = PaymentSheetHelper().getAddressCollectionModeValue(mode: val) + default: + return + } + }) + } + + let defaultBillingDetails = call.getObject("defaultBillingDetails") ?? nil + if defaultBillingDetails != nil { + defaultBillingDetails?.forEach({ (key: String, value: JSValue) in + switch key { + case "email": + if let val = value as? String { + configuration.defaultBillingDetails.email = val + } case "name": - configuration.billingDetailsCollectionConfiguration.name = getCollectionModeValue(mode: val) + if let val = value as? String { + configuration.defaultBillingDetails.name = val + } case "phone": - configuration.billingDetailsCollectionConfiguration.phone = getCollectionModeValue(mode: val) + if let val = value as? String { + configuration.defaultBillingDetails.phone = val + } case "address": - configuration.billingDetailsCollectionConfiguration.address = getAddressCollectionModeValue(mode: val) + if let val = value as? JSObject { + let address = PaymentSheet.Address( + city: val["city"] as? String, + country: val["country"] as? String, + line1: val["line1"] as? String, + line2: val["line2"] as? String, + postalCode: val["postalCode"] as? String, + state: val["state"] as? String + ) + configuration.defaultBillingDetails.address = address + } default: return } @@ -112,25 +153,4 @@ class PaymentSheetExecutor: NSObject { } } - func getCollectionModeValue(mode: String) -> PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode { - switch mode { - case "always": - return .always - case "never": - return .never - default: - return .automatic - } - } - - func getAddressCollectionModeValue(mode: String) -> PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode { - switch mode { - case "full": - return .full - case "never": - return .never - default: - return .automatic - } - } } diff --git a/packages/payment/ios/Sources/StripePlugin/PaymentSheetHelper.swift b/packages/payment/ios/Sources/StripePlugin/PaymentSheetHelper.swift new file mode 100644 index 00000000..98be5c0c --- /dev/null +++ b/packages/payment/ios/Sources/StripePlugin/PaymentSheetHelper.swift @@ -0,0 +1,25 @@ +import StripePaymentSheet + +class PaymentSheetHelper { + func getCollectionModeValue(mode: String) -> PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode { + switch mode { + case "always": + return .always + case "never": + return .never + default: + return .automatic + } + } + + func getAddressCollectionModeValue(mode: String) -> PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode { + switch mode { + case "full": + return .full + case "never": + return .never + default: + return .automatic + } + } +} diff --git a/packages/payment/package-lock.json b/packages/payment/package-lock.json index afd1ba3f..cfe5ccec 100644 --- a/packages/payment/package-lock.json +++ b/packages/payment/package-lock.json @@ -1,12 +1,12 @@ { "name": "@capacitor-community/stripe", - "version": "7.1.2", + "version": "7.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@capacitor-community/stripe", - "version": "7.1.2", + "version": "7.2.2", "license": "MIT", "devDependencies": { "@capacitor/android": "^7.0.0", diff --git a/packages/payment/package.json b/packages/payment/package.json index 9380db23..181a4fcb 100644 --- a/packages/payment/package.json +++ b/packages/payment/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor-community/stripe", - "version": "6.2.1", + "version": "7.2.2", "engines": { "node": ">=20.0.0" }, diff --git a/packages/payment/src/shared/index.ts b/packages/payment/src/shared/index.ts index 98908b29..d309aa0f 100644 --- a/packages/payment/src/shared/index.ts +++ b/packages/payment/src/shared/index.ts @@ -42,6 +42,32 @@ export type AddressCollectionMode = 'automatic' | 'full' | 'never'; */ export type CollectionMode = 'automatic' | 'always' | 'never'; +export interface Address { + /** + * Two-letter country code (ISO 3166-1 alpha-2). + */ + country?: string; + city?: string; + line1?: string; + line2?: string; + postalCode?: string; + state?: string; +} + +export interface AddressDetails { + name?: string; + address?: Address; + phone?: string; + isCheckboxSelected?: boolean; +} + +export interface DefaultBillingDetails { + email?: string; + name?: string; + phone?: string; + address?: Address; +} + interface BillingDetailsCollectionConfiguration { /** * Configuration for how billing details are collected during checkout. @@ -53,8 +79,24 @@ interface BillingDetailsCollectionConfiguration { } export interface BasePaymentOption { + /** + * Optional defaultBillingDetails + * This is ios/android only. not support web. + * https://docs.stripe.com/payments/mobile/collect-addresses?payment-ui=mobile&platform=ios#set-default-billing-details + */ + defaultBillingDetails?: DefaultBillingDetails; + + /** + * Optional shippingDetails + * This is android only. ios requires an address element. + * https://docs.stripe.com/payments/mobile/collect-addresses?payment-ui=mobile&platform=android#prefill-addresses + */ + shippingDetails?: AddressDetails; + /** * Optional billingDetailsCollectionConfiguration + * This is ios/android only. not support web. + * https://docs.stripe.com/payments/mobile/collect-addresses?payment-ui=mobile&platform=ios#customize-billing-details-collection */ billingDetailsCollectionConfiguration?: BillingDetailsCollectionConfiguration; @@ -111,6 +153,12 @@ export interface BasePaymentOption { */ returnURL?: string | undefined; + /** + * @url https://docs.stripe.com/payments/accept-a-payment?platform=android#android-customization + * @default "automatic" + */ + paymentMethodLayout?: 'horizontal' | 'vertical' | 'automatic' | undefined; + /** * iOS Only * @url https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=payment-sheet#userinterfacestyle diff --git a/packages/terminal/Package.swift b/packages/terminal/Package.swift index e69de29b..2c088442 100644 --- a/packages/terminal/Package.swift +++ b/packages/terminal/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "CapacitorCommunityStripeTerminal", + platforms: [.iOS(.v14)], + products: [ + .library( + name: "CapacitorCommunityStripeTerminal", + targets: ["StripeTerminalPlugin"]) + ], + dependencies: [ + .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0"), + .package(url: "https://github.com/stripe/stripe-terminal-ios.git", exact: "4.7.3") + ], + targets: [ + .target( + name: "StripeTerminalPlugin", + dependencies: [ + .product(name: "Capacitor", package: "capacitor-swift-pm"), + .product(name: "Cordova", package: "capacitor-swift-pm"), + .product(name: "StripeTerminal", package: "stripe-terminal-ios") + ], + path: "ios/Sources/StripeTerminalPlugin"), + .testTarget( + name: "StripeTerminalPluginTests", + dependencies: ["StripeTerminalPlugin"], + path: "ios/Tests/StripeTerminalPluginTests") + ] +) diff --git a/packages/terminal/README.md b/packages/terminal/README.md index 4d5c8b7e..89e99e48 100644 --- a/packages/terminal/README.md +++ b/packages/terminal/README.md @@ -286,12 +286,12 @@ initialize(options: { tokenProviderEndpoint?: string; isTest: boolean; }) => Pro ### discoverReaders(...) ```typescript -discoverReaders(options: { type: TerminalConnectTypes; locationId?: string; }) => Promise<{ readers: ReaderInterface[]; }> +discoverReaders(options: DiscoverReadersOptions) => Promise<{ readers: ReaderInterface[]; }> ``` -| Param | Type | -| ------------- | ----------------------------------------------------------------------------------------------------- | -| **`options`** | { type: TerminalConnectTypes; locationId?: string; } | +| Param | Type | +| ------------- | ------------------------------------------------------------------------- | +| **`options`** | DiscoverReadersOptions | **Returns:** Promise<{ readers: ReaderInterface[]; }> @@ -534,6 +534,9 @@ addListener(eventName: TerminalEventsEnum.RequestedConnectionToken, listenerFunc addListener(eventName: TerminalEventsEnum.DiscoveredReaders, listenerFunc: ({ readers }: { readers: ReaderInterface[]; }) => void) => Promise ``` +When searching for iOS Bluetooth, this will be executed multiple times. +https://docs.stripe.com/terminal/payments/connect-reader?terminal-sdk-platform=ios&reader-type=bluetooth + | Param | Type | | ------------------ | ----------------------------------------------------------------------------------- | | **`eventName`** | TerminalEventsEnum.DiscoveredReaders | @@ -965,6 +968,15 @@ addListener(eventName: TerminalEventsEnum.ReaderReconnectFailed, listenerFunc: ( ### Interfaces +#### DiscoverReadersOptions + +| Prop | Type | Description | +| --------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`type`** | TerminalConnectTypes | | +| **`locationId`** | string | | +| **`bluetoothScanWaitTime`** | number | Only applies to Bluetooth scan discovery (iOS only). During discovery, readers are reported via DiscoveryDelegate.didUpdateDiscoveredReaders. This timeout controls how long to wait before resolving the `discoverReaders` method with the current list. If this setting is not specified or is set to 0, the initial scan results will be returned. | + + #### PluginListenerHandle | Prop | Type | diff --git a/packages/terminal/SimPROSoftwareStripeTerminal.podspec b/packages/terminal/SimPROSoftwareStripeTerminal.podspec index 1e64e062..0f1fa1aa 100644 --- a/packages/terminal/SimPROSoftwareStripeTerminal.podspec +++ b/packages/terminal/SimPROSoftwareStripeTerminal.podspec @@ -13,6 +13,6 @@ Pod::Spec.new do |s| s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}' s.ios.deployment_target = '14.0' s.dependency 'Capacitor' - s.dependency 'StripeTerminal', '~> 4.5.0' + s.dependency 'StripeTerminal', '4.7.3' s.swift_version = '5.1' end diff --git a/packages/terminal/android/build.gradle b/packages/terminal/android/build.gradle index 179d24cd..cbc73e99 100644 --- a/packages/terminal/android/build.gradle +++ b/packages/terminal/android/build.gradle @@ -6,9 +6,9 @@ ext { playServicesWalletVersion = project.hasProperty('playServicesWalletVersion') ? rootProject.ext.playServicesWalletVersion : '19.2.+' volleyVersion = project.hasProperty('volleyVersion') ? rootProject.ext.volleyVersion : '1.2.1' - stripeterminalTapToPayVersion = project.hasProperty('stripeterminalTapToPayVersion') ? rootProject.ext.stripeterminalTapToPayVersion : '4.5.1' - stripeterminalCoreVersion = project.hasProperty('stripeterminalCoreVersion') ? rootProject.ext.stripeterminalCoreVersion : '4.5.1' - stripeterminalHandoffClientVersion = project.hasProperty('stripeterminalHandoffClientVersion') ? rootProject.ext.stripeterminalHandoffClientVersion : '4.5.1' + stripeterminalTapToPayVersion = project.hasProperty('stripeterminalTapToPayVersion') ? rootProject.ext.stripeterminalTapToPayVersion : '4.7.+' + stripeterminalCoreVersion = project.hasProperty('stripeterminalCoreVersion') ? rootProject.ext.stripeterminalCoreVersion : '4.7.+' + stripeterminalHandoffClientVersion = project.hasProperty('stripeterminalHandoffClientVersion') ? rootProject.ext.stripeterminalHandoffClientVersion : '4.7.+' } buildscript { diff --git a/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/StripeTerminal.kt b/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/StripeTerminal.kt index 63840a1b..11a032aa 100644 --- a/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/StripeTerminal.kt +++ b/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/StripeTerminal.kt @@ -207,6 +207,11 @@ class StripeTerminal( return } + notifyListeners( + TerminalEnumEvent.DiscoveringReaders.webEventName, + emptyObject + ) + discoveryCancelable = Terminal.getInstance() .discoverReaders( config, diff --git a/packages/terminal/ios/Sources/StripeTerminalPlugin/StripeTerminal.swift b/packages/terminal/ios/Sources/StripeTerminalPlugin/StripeTerminal.swift index 4b633477..47a81de3 100644 --- a/packages/terminal/ios/Sources/StripeTerminalPlugin/StripeTerminal.swift +++ b/packages/terminal/ios/Sources/StripeTerminalPlugin/StripeTerminal.swift @@ -58,7 +58,27 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, TerminalDelegate, Read return } - self.discoverCall = call + self.plugin?.notifyListeners(TerminalEvents.DiscoveringReaders.rawValue, data: [:]) + + + let bluetoothScanWaitTime = call.getDouble("bluetoothScanWaitTime") ?? 0 + if (self.type == .bluetoothScan && bluetoothScanWaitTime != 0) { + // When bluetoothScanWaitTime is non-zero, we defer the resolution of the call + // using a timer. In this case, discoverCall is not assigned because the resolution + // logic is handled asynchronously after the wait time. + DispatchQueue.main.asyncAfter(deadline: .now() + bluetoothScanWaitTime / 1000.0) { + var readersJSObject: JSArray = [] + for reader in self.discoveredReadersList ?? [] { + readersJSObject.append(self.convertReaderInterface(reader: reader)) + } + call.resolve([ + "readers": readersJSObject + ]) + } + } else { + self.discoverCall = call + } + self.discoverCancelable = Terminal.shared.discoverReaders(config, delegate: self) { error in if let error = error { print("discoverReaders failed: \(error)") @@ -78,6 +98,8 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, TerminalDelegate, Read self.discoveredReadersList = readers self.plugin?.notifyListeners(TerminalEvents.DiscoveredReaders.rawValue, data: ["readers": readersJSObject]) + + // If self.type == .bluetoothScan, discoverCall is nil self.discoverCall?.resolve([ "readers": readersJSObject ]) @@ -401,6 +423,7 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, TerminalDelegate, Read let errorDetails: [String: Any] = ["message": error.localizedDescription, "code": String((error as NSError).code)] call.reject(error.localizedDescription, nil, nil, errorDetails) } else { + self.plugin?.notifyListeners(TerminalEvents.CancelDiscoveredReaders.rawValue, data: [:]) call.resolve() } } diff --git a/packages/terminal/ios/Sources/StripeTerminalPlugin/TerminalEvents.swift b/packages/terminal/ios/Sources/StripeTerminalPlugin/TerminalEvents.swift index ab2115f1..31d53ebb 100644 --- a/packages/terminal/ios/Sources/StripeTerminalPlugin/TerminalEvents.swift +++ b/packages/terminal/ios/Sources/StripeTerminalPlugin/TerminalEvents.swift @@ -3,6 +3,7 @@ public enum TerminalEvents: String { case DiscoveringReaders = "terminalDiscoveringReaders" case DiscoveredReaders = "terminalDiscoveredReaders" case ConnectedReader = "terminalConnectedReader" + case CancelDiscoveredReaders = "terminalCancelDiscoveredReaders" case DisconnectedReader = "terminalDisconnectedReader" case ConnectionStatusChange = "terminalConnectionStatusChange" case UnexpectedReaderDisconnect = "terminalUnexpectedReaderDisconnect" diff --git a/packages/terminal/package-lock.json b/packages/terminal/package-lock.json index c8a77a6a..f96c6dd5 100644 --- a/packages/terminal/package-lock.json +++ b/packages/terminal/package-lock.json @@ -1,15 +1,15 @@ { "name": "@simPRO-Software/stripe-terminal", - "version": "7.0.0", + "version": "7.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@simPRO-Software/stripe-terminal", - "version": "7.0.0", + "version": "7.2.2", "license": "MIT", "dependencies": { - "@stripe/terminal-js": "^0.13.0" + "@stripe/terminal-js": "^0.24.0" }, "devDependencies": { "@capacitor/android": "^7.0.0", @@ -869,11 +869,13 @@ ] }, "node_modules/@stripe/terminal-js": { - "version": "0.13.0", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@stripe/terminal-js/-/terminal-js-0.24.0.tgz", + "integrity": "sha512-fStlSPqit2t99o40ZCFUZe4WiB0J+tQDND4Kcy+5mIq3fqd9PF4QBVYlaC5iAc05Zu0iSH9UhA4QOC+feJC2sQ==", "license": "MIT", "dependencies": { - "stripe": "^8.184.0", - "ws": "6.2.2" + "stripe": "^8.222.0", + "ws": "6.2.3" } }, "node_modules/@types/estree": { @@ -1319,6 +1321,8 @@ }, "node_modules/async-limiter": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "license": "MIT" }, "node_modules/at-least-node": { @@ -1348,7 +1352,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2333,6 +2339,16 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/glob/node_modules/minimatch": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", @@ -4286,7 +4302,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "6.2.2", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", "license": "MIT", "dependencies": { "async-limiter": "~1.0.0" diff --git a/packages/terminal/package.json b/packages/terminal/package.json index b6610fce..2c6ca8b3 100644 --- a/packages/terminal/package.json +++ b/packages/terminal/package.json @@ -1,6 +1,6 @@ { "name": "@simPRO-Software/stripe-terminal", - "version": "7.0.0", + "version": "7.2.2", "engines": { "node": ">=18.0.0" }, @@ -79,6 +79,6 @@ } }, "dependencies": { - "@stripe/terminal-js": "^0.13.0" + "@stripe/terminal-js": "^0.24.0" } } diff --git a/packages/terminal/src/definitions.ts b/packages/terminal/src/definitions.ts index 6bf0182c..0ffece69 100644 --- a/packages/terminal/src/definitions.ts +++ b/packages/terminal/src/definitions.ts @@ -91,11 +91,25 @@ export type Cart = { lineItems: CartLineItem[]; }; +export interface DiscoverReadersOptions { + type: TerminalConnectTypes; + locationId?: string; + + /** + * Only applies to Bluetooth scan discovery (iOS only). + * During discovery, readers are reported via DiscoveryDelegate.didUpdateDiscoveredReaders. + * This timeout controls how long to wait before resolving the `discoverReaders` method with the current list. + * + * If this setting is not specified or is set to 0, the initial scan results will be returned. + */ + bluetoothScanWaitTime?: number; +} + export * from './events.enum'; export * from './stripe.enum'; export interface StripeTerminalPlugin { initialize(options: { tokenProviderEndpoint?: string; isTest: boolean }): Promise; - discoverReaders(options: { type: TerminalConnectTypes; locationId?: string }): Promise<{ + discoverReaders(options: DiscoverReadersOptions): Promise<{ readers: ReaderInterface[]; }>; setConnectionToken(options: { token: string }): Promise; @@ -155,6 +169,10 @@ export interface StripeTerminalPlugin { listenerFunc: () => void, ): Promise; + /** + * When searching for iOS Bluetooth, this will be executed multiple times. + * https://docs.stripe.com/terminal/payments/connect-reader?terminal-sdk-platform=ios&reader-type=bluetooth + */ addListener( eventName: TerminalEventsEnum.DiscoveredReaders, listenerFunc: ({ readers }: { readers: ReaderInterface[] }) => void,