├── fastlane ├── Appfile ├── report.xml ├── README.md └── Fastfile ├── android ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ └── .gitkeep │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── io │ │ │ └── qonversion │ │ │ └── capacitor │ │ │ ├── extenstions.kt │ │ │ └── QonversionPlugin.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── getcapacitor │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── getcapacitor │ │ └── android │ │ └── ExampleInstrumentedTest.java ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── settings.gradle ├── proguard-rules.pro ├── gradle.properties ├── build.gradle ├── gradlew.bat └── gradlew ├── example ├── android │ ├── app │ │ ├── .gitignore │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── drawable │ │ │ │ │ │ ├── splash.png │ │ │ │ │ │ └── ic_launcher_background.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ │ ├── drawable-land-hdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-land-mdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-hdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-mdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ │ ├── drawable-land-xhdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-land-xxhdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-land-xxxhdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-xhdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-xxhdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-xxxhdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ │ ├── values │ │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ │ ├── strings.xml │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── xml │ │ │ │ │ │ └── file_paths.xml │ │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ │ ├── layout │ │ │ │ │ │ └── activity_main.xml │ │ │ │ │ └── drawable-v24 │ │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ ├── java │ │ │ │ │ └── com │ │ │ │ │ │ └── qonversion │ │ │ │ │ │ └── sample │ │ │ │ │ │ └── MainActivity.java │ │ │ │ └── AndroidManifest.xml │ │ │ ├── test │ │ │ │ └── java │ │ │ │ │ └── com │ │ │ │ │ └── getcapacitor │ │ │ │ │ └── myapp │ │ │ │ │ └── ExampleUnitTest.java │ │ │ └── androidTest │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── getcapacitor │ │ │ │ └── myapp │ │ │ │ └── ExampleInstrumentedTest.java │ │ ├── capacitor.build.gradle │ │ ├── proguard-rules.pro │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ ├── variables.gradle │ ├── capacitor.settings.gradle │ ├── build.gradle │ ├── gradle.properties │ ├── .gitignore │ ├── gradlew.bat │ └── gradlew ├── .gitignore ├── ios │ ├── App │ │ ├── App │ │ │ ├── Assets.xcassets │ │ │ │ ├── Contents.json │ │ │ │ ├── Splash.imageset │ │ │ │ │ ├── splash-2732x2732.png │ │ │ │ │ ├── splash-2732x2732-1.png │ │ │ │ │ ├── splash-2732x2732-2.png │ │ │ │ │ └── Contents.json │ │ │ │ └── AppIcon.appiconset │ │ │ │ │ ├── AppIcon-512@2x.png │ │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj │ │ │ │ ├── Main.storyboard │ │ │ │ └── LaunchScreen.storyboard │ │ │ ├── Info.plist │ │ │ └── AppDelegate.swift │ │ ├── App.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── Podfile │ │ ├── Podfile.lock │ │ └── App.xcodeproj │ │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── App.xcscheme │ └── .gitignore ├── vite.config.ts ├── capacitor.config.json ├── README.md ├── src │ ├── index.css │ ├── index.html │ └── js │ │ └── index.js └── package.json ├── ios ├── .gitignore ├── Tests │ └── QonversionPluginTests │ │ └── QonversionPluginTests.swift └── Sources │ └── QonversionPlugin │ └── Extensions.swift ├── .github └── workflows │ ├── stale_issues.yml │ ├── release_pull_requests.yml │ ├── manual_minor_prerelease.yml │ ├── manual_patch_prerelease.yml │ ├── checks.yml │ ├── upgrade_sandwich.yml │ ├── prerelease_github.yml │ └── publish.yml ├── src ├── dto │ ├── User.ts │ ├── IntroEligibility.ts │ ├── storeProducts │ │ ├── SKSubscriptionPeriod.ts │ │ ├── ProductInAppDetails.ts │ │ ├── SKPaymentDiscount.ts │ │ ├── ProductInstallmentPlanDetails.ts │ │ ├── ProductPrice.ts │ │ ├── SKProductDiscount.ts │ │ ├── ProductPricingPhase.ts │ │ ├── SKProduct.ts │ │ ├── SkuDetails.ts │ │ ├── ProductOfferDetails.ts │ │ └── ProductStoreDetails.ts │ ├── Experiment.ts │ ├── ExperimentGroup.ts │ ├── EntitlementsUpdateListener.ts │ ├── Offerings.ts │ ├── Offering.ts │ ├── QonversionError.ts │ ├── RemoteConfig.ts │ ├── ActionResult.ts │ ├── UserProperty.ts │ ├── PromotionalOffer.ts │ ├── PromoPurchasesListener.ts │ ├── SubscriptionPeriod.ts │ ├── RemoteConfigurationSource.ts │ ├── RemoteConfigList.ts │ ├── PurchaseOptions.ts │ ├── AutomationsDelegate.ts │ ├── Transaction.ts │ ├── Entitlement.ts │ ├── UserProperties.ts │ ├── Product.ts │ └── PurchaseOptionsBuilder.ts ├── internal │ ├── utils.ts │ ├── web.ts │ └── QonversionInternal.ts ├── QonversionConfig.ts ├── index.ts ├── Qonversion.ts ├── QonversionNativePlugin.ts └── QonversionConfigBuilder.ts ├── Package.resolved ├── rollup.config.js ├── tsconfig.json ├── QonversionCapacitorPlugin.podspec ├── Package.swift ├── .gitignore ├── package.json └── README.md /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android/src/main/res/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build/* 2 | !/build/.npmkeep 3 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | .vscode/ 4 | *.map 5 | .DS_Store 6 | .sourcemaps 7 | dist/ 8 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /example/ios/App/App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':capacitor-android' 2 | project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/drawable/splash.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /fastlane/report.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-land-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/drawable-land-hdpi/splash.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-land-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/drawable-land-mdpi/splash.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-port-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/drawable-port-hdpi/splash.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-port-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/drawable-port-mdpi/splash.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-land-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/drawable-land-xhdpi/splash.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-land-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/drawable-land-xxhdpi/splash.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-land-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/drawable-land-xxxhdpi/splash.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-port-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/drawable-port-xhdpi/splash.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-port-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/drawable-port-xxhdpi/splash.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-port-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/drawable-port-xxxhdpi/splash.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png -------------------------------------------------------------------------------- /example/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png -------------------------------------------------------------------------------- /example/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/capacitor-plugin/HEAD/example/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/qonversion/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.qonversion.sample; 2 | 3 | import com.getcapacitor.BridgeActivity; 4 | 5 | public class MainActivity extends BridgeActivity {} 6 | -------------------------------------------------------------------------------- /.github/workflows/stale_issues.yml: -------------------------------------------------------------------------------- 1 | name: Close stale issues 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale_issues: 8 | uses: qonversion/shared-sdk-workflows/.github/workflows/stale_issues.yml@main -------------------------------------------------------------------------------- /example/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | root: './src', 5 | build: { 6 | outDir: '../dist', 7 | minify: false, 8 | emptyOutDir: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /example/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "com.qonversion.sample", 3 | "appName": "Qonversion Sample", 4 | "webDir": "dist", 5 | "plugins": { 6 | "SplashScreen": { 7 | "launchShowDuration": 0 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':capacitor-cordova-android-plugins' 3 | project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') 4 | 5 | apply from: 'capacitor.settings.gradle' -------------------------------------------------------------------------------- /example/android/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/dto/User.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | qonversionId: string; 3 | identityId?: string | null; 4 | 5 | constructor(qonversionId: string, identityId?: string | null) { 6 | this.qonversionId = qonversionId; 7 | this.identityId = identityId; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/dto/IntroEligibility.ts: -------------------------------------------------------------------------------- 1 | import { IntroEligibilityStatus } from "./enums"; 2 | 3 | export class IntroEligibility { 4 | status?: IntroEligibilityStatus; 5 | 6 | constructor(status: IntroEligibilityStatus | undefined) { 7 | this.status = status; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/release_pull_requests.yml: -------------------------------------------------------------------------------- 1 | name: Release pull requests from dev by tag 2 | on: 3 | push: 4 | tags: 5 | - prerelease/* 6 | 7 | jobs: 8 | handle_prerelease: 9 | uses: qonversion/shared-sdk-workflows/.github/workflows/prerelease_handling.yml@main 10 | -------------------------------------------------------------------------------- /src/internal/utils.ts: -------------------------------------------------------------------------------- 1 | import {Capacitor} from "@capacitor/core"; 2 | 3 | export const isIos = (): boolean => { 4 | return Capacitor.getPlatform() === "ios" 5 | }; 6 | 7 | export const isAndroid = (): boolean => { 8 | return Capacitor.getPlatform() === "android" 9 | }; 10 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | App/build 2 | App/Pods 3 | App/output 4 | App/App/public 5 | DerivedData 6 | xcuserdata 7 | 8 | # Cordova plugins for Capacitor 9 | capacitor-cordova-ios-plugins 10 | 11 | # Generated Config files 12 | App/App/capacitor.config.json 13 | App/App/config.xml 14 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /example/ios/App/App.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /example/ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /example/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-512@2x.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/dto/storeProducts/SKSubscriptionPeriod.ts: -------------------------------------------------------------------------------- 1 | import {SKPeriodUnits} from "../enums"; 2 | 3 | export class SKSubscriptionPeriod { 4 | numberOfUnits: number; 5 | unit: SKPeriodUnits; 6 | 7 | constructor(numberOfUnits: number, unit: SKPeriodUnits) { 8 | this.numberOfUnits = numberOfUnits; 9 | this.unit = unit; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/manual_minor_prerelease.yml: -------------------------------------------------------------------------------- 1 | name: Manual Minor Prerelease 2 | 3 | on: 4 | workflow_dispatch 5 | 6 | jobs: 7 | patch-minor: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | ref: develop 14 | 15 | - name: Minor 16 | run: | 17 | fastlane minor 18 | -------------------------------------------------------------------------------- /src/dto/Experiment.ts: -------------------------------------------------------------------------------- 1 | import {ExperimentGroup} from './ExperimentGroup'; 2 | 3 | export class Experiment { 4 | id: string; 5 | name: string; 6 | group: ExperimentGroup; 7 | 8 | constructor(id: string, name: string, group: ExperimentGroup) { 9 | this.id = id; 10 | this.name = name; 11 | this.group = group; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/manual_patch_prerelease.yml: -------------------------------------------------------------------------------- 1 | name: Manual Patch Prerelease 2 | 3 | on: 4 | workflow_dispatch 5 | 6 | jobs: 7 | patch-prerelease: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | ref: develop 14 | 15 | - name: Patch 16 | run: | 17 | fastlane patch 18 | -------------------------------------------------------------------------------- /src/dto/ExperimentGroup.ts: -------------------------------------------------------------------------------- 1 | import {ExperimentGroupType} from './enums'; 2 | 3 | export class ExperimentGroup { 4 | id: string; 5 | name: string; 6 | type: ExperimentGroupType; 7 | 8 | constructor(id: string, name: string, type: ExperimentGroupType) { 9 | this.id = id; 10 | this.name = name; 11 | this.type = type; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ## Created with Capacitor Create App 2 | 3 | This app was created using [`@capacitor/create-app`](https://github.com/ionic-team/create-capacitor-app), 4 | and comes with a very minimal shell for building an app. 5 | 6 | ### Running this example 7 | 8 | To run the provided example, you can use `npm start` command. 9 | 10 | ```bash 11 | npm start 12 | ``` 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Qonversion Sample 4 | Qonversion Sample 5 | com.qonversion.sample 6 | com.qonversion.sample 7 | 8 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "capacitor-swift-pm", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/ionic-team/capacitor-swift-pm.git", 7 | "state" : { 8 | "branch" : "main", 9 | "revision" : "272e5dcd26260c6f3798b024c9cb3ffa52850ec7" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: Checks 2 | on: 3 | pull_request: 4 | types: [opened, synchronize, reopened] 5 | 6 | jobs: 7 | lint: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | name: Checkout 13 | 14 | - uses: borales/actions-yarn@v3.0.0 15 | name: Validation 16 | with: 17 | cmd: install 18 | -------------------------------------------------------------------------------- /src/dto/storeProducts/ProductInAppDetails.ts: -------------------------------------------------------------------------------- 1 | import {ProductPrice} from "./ProductPrice"; 2 | 3 | /** 4 | * This class contains all the information about the Google in-app product details. 5 | */ 6 | export class ProductInAppDetails { 7 | /** 8 | * The price of the in-app product. 9 | */ 10 | price: ProductPrice; 11 | 12 | constructor(price: ProductPrice) { 13 | this.price = price; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/dto/EntitlementsUpdateListener.ts: -------------------------------------------------------------------------------- 1 | import {Entitlement} from './Entitlement'; 2 | 3 | export interface EntitlementsUpdateListener { 4 | 5 | /** 6 | * Called when entitlements update. 7 | * For example, when pending purchases like SCA, Ask to buy, etc., happen. 8 | * @param entitlements all the client's entitlements after update. 9 | */ 10 | onEntitlementsUpdated(entitlements: Map): void; 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/upgrade_sandwich.yml: -------------------------------------------------------------------------------- 1 | name: Upgrade Sandwich 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | sandwich_version: 6 | description: 'Sandwich version' 7 | required: true 8 | default: '0.0.0' 9 | 10 | jobs: 11 | upgrade: 12 | uses: qonversion/shared-sdk-workflows/.github/workflows/upgrade_sandwich.yml@main 13 | with: 14 | sandwich_version: ${{ github.event.inputs.sandwich_version }} 15 | -------------------------------------------------------------------------------- /src/dto/Offerings.ts: -------------------------------------------------------------------------------- 1 | import {Offering} from "./Offering"; 2 | 3 | export class Offerings { 4 | main: Offering | null; 5 | availableOffering: Array; 6 | 7 | constructor(main: Offering | null, availableOfferings: Array) { 8 | this.main = main; 9 | this.availableOffering = availableOfferings; 10 | } 11 | 12 | offeringForIdentifier(identifier: string): Offering | undefined { 13 | return this.availableOffering.find((object) => object.id === identifier); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/src/test/java/com/getcapacitor/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | 14 | @Test 15 | public void addition_isCorrect() throws Exception { 16 | assertEquals(4, 2 + 2); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/prerelease_github.yml: -------------------------------------------------------------------------------- 1 | name: Pre-release Github 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | 8 | jobs: 9 | pre-release: 10 | runs-on: macos-latest 11 | 12 | steps: 13 | - uses: "marvinpinto/action-automatic-releases@latest" 14 | with: 15 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 16 | automatic_release_tag: "latest" 17 | prerelease: true 18 | title: "Development Build" 19 | files: | 20 | LICENSE.txt 21 | *.jar 22 | -------------------------------------------------------------------------------- /src/dto/Offering.ts: -------------------------------------------------------------------------------- 1 | import {OfferingTags} from './enums'; 2 | import {Product} from './Product'; 3 | 4 | export class Offering { 5 | id: string; 6 | tag: OfferingTags; 7 | products: Array; 8 | 9 | constructor(id: string, tag: OfferingTags, products: Product[]) { 10 | this.id = id; 11 | this.tag = tag; 12 | this.products = products; 13 | } 14 | 15 | productForIdentifier(identifier: string): Product | undefined { 16 | return this.products.find((object) => object.qonversionID === identifier); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | 14 | @Test 15 | public void addition_isCorrect() throws Exception { 16 | assertEquals(4, 2 + 2); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/dto/QonversionError.ts: -------------------------------------------------------------------------------- 1 | import {QonversionErrorCode} from './enums'; 2 | 3 | export class QonversionError { 4 | code: QonversionErrorCode; 5 | domain?: string; 6 | description: string; 7 | additionalMessage: string; 8 | 9 | constructor( 10 | code: QonversionErrorCode, 11 | description: string, 12 | additionalMessage: string, 13 | domain?: string, 14 | ) { 15 | this.code = code; 16 | this.domain = domain; 17 | this.description = description; 18 | this.additionalMessage = additionalMessage; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/dto/RemoteConfig.ts: -------------------------------------------------------------------------------- 1 | import {Experiment} from './Experiment'; 2 | import {RemoteConfigurationSource} from './RemoteConfigurationSource'; 3 | 4 | export class RemoteConfig { 5 | payload: Record; 6 | experiment?: Experiment | null; 7 | source: RemoteConfigurationSource; 8 | 9 | constructor(payload: Record, experiment: Experiment | null, source: RemoteConfigurationSource) { 10 | this.payload = payload; 11 | this.experiment = experiment; 12 | this.source = source; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "splash-2732x2732-2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "splash-2732x2732-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "splash-2732x2732.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /ios/Tests/QonversionPluginTests/QonversionPluginTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import QonversionPlugin 3 | 4 | class QonversionTests: XCTestCase { 5 | func testEcho() { 6 | // This is an example of a functional test case for a plugin. 7 | // Use XCTAssert and related functions to verify your tests produce the correct results. 8 | 9 | let implementation = Qonversion() 10 | let value = "Hello, World!" 11 | let result = implementation.echo(value) 12 | 13 | XCTAssertEqual(value, result) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | input: 'dist/esm/index.js', 3 | output: [ 4 | { 5 | file: 'dist/plugin.js', 6 | format: 'iife', 7 | name: 'capacitorQonversion', 8 | globals: { 9 | '@capacitor/core': 'capacitorExports', 10 | }, 11 | sourcemap: true, 12 | inlineDynamicImports: true, 13 | }, 14 | { 15 | file: 'dist/plugin.cjs.js', 16 | format: 'cjs', 17 | sourcemap: true, 18 | inlineDynamicImports: true, 19 | }, 20 | ], 21 | external: ['@capacitor/core'], 22 | }; 23 | -------------------------------------------------------------------------------- /src/dto/ActionResult.ts: -------------------------------------------------------------------------------- 1 | import {ActionResultType} from './enums'; 2 | import {QonversionError} from './QonversionError'; 3 | 4 | export class ActionResult { 5 | 6 | type: ActionResultType; 7 | value: Map | undefined; 8 | error: QonversionError | undefined; 9 | 10 | constructor( 11 | type: ActionResultType, 12 | value: Map | undefined, 13 | error: QonversionError | undefined, 14 | ) { 15 | this.type = type; 16 | this.value = value; 17 | this.error = error; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/dto/UserProperty.ts: -------------------------------------------------------------------------------- 1 | import {UserPropertyKey} from './enums'; 2 | import mapper from '../internal/Mapper'; 3 | 4 | export class UserProperty { 5 | key: string; 6 | value: string; 7 | 8 | /** 9 | * {@link UserPropertyKey} used to set this property. 10 | * Returns {@link UserPropertyKey.CUSTOM} for custom properties. 11 | */ 12 | definedKey: UserPropertyKey; 13 | 14 | constructor(key: string, value: string) { 15 | this.key = key; 16 | this.value = value; 17 | this.definedKey = mapper.convertDefinedUserPropertyKey(key); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowUnreachableCode": false, 4 | "declaration": true, 5 | "esModuleInterop": true, 6 | "inlineSources": true, 7 | "lib": ["dom", "es2017"], 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "noFallthroughCasesInSwitch": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": false, 13 | "outDir": "dist/esm", 14 | "pretty": true, 15 | "sourceMap": true, 16 | "strict": true, 17 | "target": "es2017" 18 | }, 19 | "files": ["src/index.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /src/dto/PromotionalOffer.ts: -------------------------------------------------------------------------------- 1 | import {SKProductDiscount} from './storeProducts/SKProductDiscount'; 2 | import {SKPaymentDiscount} from './storeProducts/SKPaymentDiscount'; 3 | 4 | export class PromotionalOffer { 5 | public readonly productDiscount: SKProductDiscount; 6 | public readonly paymentDiscount: SKPaymentDiscount; 7 | 8 | constructor ( 9 | productDiscount: SKProductDiscount, 10 | paymentDiscount: SKPaymentDiscount 11 | ) { 12 | this.productDiscount = productDiscount; 13 | this.paymentDiscount = paymentDiscount; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/android/variables.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | minSdkVersion = 22 3 | compileSdkVersion = 35 4 | targetSdkVersion = 35 5 | androidxActivityVersion = '1.8.0' 6 | androidxAppCompatVersion = '1.6.1' 7 | androidxCoordinatorLayoutVersion = '1.2.0' 8 | androidxCoreVersion = '1.12.0' 9 | androidxFragmentVersion = '1.6.2' 10 | coreSplashScreenVersion = '1.0.1' 11 | androidxWebkitVersion = '1.9.0' 12 | junitVersion = '4.13.2' 13 | androidxJunitVersion = '1.1.5' 14 | androidxEspressoCoreVersion = '3.5.1' 15 | cordovaAndroidVersion = '10.1.1' 16 | } 17 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /src/dto/storeProducts/SKPaymentDiscount.ts: -------------------------------------------------------------------------------- 1 | export class SKPaymentDiscount { 2 | identifier: string; 3 | keyIdentifier: string; 4 | nonce: string; 5 | signature: string; 6 | timestamp: number; 7 | 8 | constructor ( 9 | identifier: string, 10 | keyIdentifier: string, 11 | nonce: string, 12 | signature: string, 13 | timestamp: number, 14 | ) { 15 | this.identifier = identifier; 16 | this.keyIdentifier = keyIdentifier; 17 | this.nonce = nonce; 18 | this.signature = signature; 19 | this.timestamp = timestamp; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/dto/PromoPurchasesListener.ts: -------------------------------------------------------------------------------- 1 | import {Entitlement} from './Entitlement'; 2 | 3 | export interface PromoPurchasesListener { 4 | 5 | /** 6 | * Fired each time a promo purchase from the App Store happens. 7 | * Call {@param promoPurchaseExecutor} in case of your app is ready to start promo purchase. 8 | * Or cache that executor and call later when you need. 9 | * @param productId StoreKit product identifier. 10 | * @param promoPurchaseExecutor a function that will start a promo purchase flow. 11 | */ 12 | onPromoPurchaseReceived(productId: string, promoPurchaseExecutor: () => Promise>): void; 13 | } 14 | -------------------------------------------------------------------------------- /ios/Sources/QonversionPlugin/Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import QonversionSandwich 3 | import Capacitor 4 | 5 | extension CAPPluginCall { 6 | func noNecessaryDataError() { 7 | reject("Could not find necessary arguments. Make sure you pass correct call arguments", "NoNecessaryDataError") 8 | } 9 | 10 | func sandwichError(_ error: SandwichError) { 11 | var message = error.details + ". Qonversion Error Code: \(error.code)" 12 | 13 | if let additionalMessage = error.additionalMessage { 14 | message = "\(message). Additional Message: \(additionalMessage)" 15 | } 16 | 17 | reject(message, error.code) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/android/app/capacitor.build.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | 3 | android { 4 | compileOptions { 5 | sourceCompatibility JavaVersion.VERSION_21 6 | targetCompatibility JavaVersion.VERSION_21 7 | } 8 | } 9 | 10 | apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" 11 | dependencies { 12 | implementation project(':capacitor-camera') 13 | implementation project(':capacitor-splash-screen') 14 | implementation project(':qonversion-capacitor-plugin') 15 | 16 | } 17 | 18 | 19 | if (hasProperty('postBuildExtras')) { 20 | postBuildExtras() 21 | } 22 | -------------------------------------------------------------------------------- /QonversionCapacitorPlugin.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = 'QonversionCapacitorPlugin' 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.license = package['license'] 10 | s.homepage = package['repository']['url'] 11 | s.author = package['author'] 12 | s.source = { :git => package['repository']['url'], :tag => s.version.to_s } 13 | s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' 14 | s.ios.deployment_target = '13.0' 15 | s.dependency 'Capacitor' 16 | s.swift_version = '5.1' 17 | s.dependency "QonversionSandwich", "5.2.1" 18 | end 19 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | publish: 9 | name: Upload archives 10 | runs-on: macos-latest 11 | steps: 12 | - name: Check out code 13 | uses: actions/checkout@v3 14 | 15 | - name: Build 16 | run: | 17 | yarn 18 | yarn build 19 | 20 | - name: Setup node for publishing 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: '16.x' 24 | registry-url: 'https://registry.npmjs.org' 25 | 26 | - name: Publish to npm 27 | run: npm publish 28 | env: 29 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 30 | -------------------------------------------------------------------------------- /example/android/capacitor.settings.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | include ':capacitor-android' 3 | project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') 4 | 5 | include ':capacitor-camera' 6 | project(':capacitor-camera').projectDir = new File('../node_modules/@capacitor/camera/android') 7 | 8 | include ':capacitor-splash-screen' 9 | project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capacitor/splash-screen/android') 10 | 11 | include ':qonversion-capacitor-plugin' 12 | project(':qonversion-capacitor-plugin').projectDir = new File('../node_modules/@qonversion/capacitor-plugin/android') 13 | -------------------------------------------------------------------------------- /example/src/index.css: -------------------------------------------------------------------------------- 1 | 2 | .button-container { 3 | display: flex; 4 | flex-direction: column; 5 | margin-inside: 10px; 6 | padding-bottom: 200px; 7 | } 8 | 9 | .qon-button { 10 | flex: 1; 11 | min-height: 30px; 12 | border-radius: 16px; 13 | border-width: 1px; 14 | } 15 | 16 | .row { 17 | display: flex; 18 | flex-direction: row; 19 | justify-content: center; 20 | align-items: center; 21 | } 22 | 23 | .qon-input { 24 | width: 100px; 25 | height: 20px; 26 | padding-left: 8px; 27 | padding-right: 8px; 28 | border-radius: 16px; 29 | border-width: 1px; 30 | margin-right: 10px; 31 | } 32 | 33 | .top-margin { 34 | margin-top: 10px; 35 | } 36 | -------------------------------------------------------------------------------- /src/dto/SubscriptionPeriod.ts: -------------------------------------------------------------------------------- 1 | import {SubscriptionPeriodUnit} from "./enums"; 2 | 3 | /** 4 | * A class describing a subscription period 5 | */ 6 | export class SubscriptionPeriod { 7 | /** 8 | * A count of subsequent intervals. 9 | */ 10 | unitCount: number; 11 | 12 | /** 13 | * Interval unit. 14 | */ 15 | unit: SubscriptionPeriodUnit; 16 | 17 | /** 18 | * ISO 8601 representation of the period, e.g. "P7D", meaning 7 days period. 19 | */ 20 | iso: string; 21 | 22 | constructor( 23 | unitCount: number, 24 | unit: SubscriptionPeriodUnit, 25 | iso: string, 26 | ) { 27 | this.unitCount = unitCount; 28 | this.unit = unit; 29 | this.iso = iso; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:8.2.1' 11 | classpath 'com.google.gms:google-services:4.4.0' 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | apply from: "variables.gradle" 19 | 20 | allprojects { 21 | repositories { 22 | google() 23 | mavenCentral() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /src/dto/RemoteConfigurationSource.ts: -------------------------------------------------------------------------------- 1 | import {RemoteConfigurationAssignmentType, RemoteConfigurationSourceType} from "./enums"; 2 | 3 | 4 | export class RemoteConfigurationSource { 5 | id: string; 6 | name: string; 7 | type: RemoteConfigurationSourceType; 8 | assignmentType: RemoteConfigurationAssignmentType; 9 | contextKey: string | null; 10 | 11 | constructor( 12 | id: string, 13 | name: string, 14 | type: RemoteConfigurationSourceType, 15 | assignmentType: RemoteConfigurationAssignmentType, 16 | contextKey: string | null, 17 | ) { 18 | this.id = id; 19 | this.name = name; 20 | this.type = type; 21 | this.assignmentType = assignmentType; 22 | this.contextKey = contextKey; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/dto/RemoteConfigList.ts: -------------------------------------------------------------------------------- 1 | import {RemoteConfig} from './RemoteConfig'; 2 | 3 | export class RemoteConfigList { 4 | remoteConfigs: Array; 5 | 6 | constructor(remoteConfigs: Array) { 7 | this.remoteConfigs = remoteConfigs; 8 | } 9 | 10 | remoteConfigForContextKey(contextKey: string): RemoteConfig | undefined { 11 | return this.findRemoteConfigForContextKey(contextKey); 12 | } 13 | 14 | remoteConfigForEmptyContextKey(): RemoteConfig | undefined { 15 | return this.findRemoteConfigForContextKey(null); 16 | } 17 | 18 | private findRemoteConfigForContextKey(contextKey: string | null): RemoteConfig | undefined { 19 | return this.remoteConfigs.find(config => config.source.contextKey == contextKey); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 17 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.android; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import android.content.Context; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | import androidx.test.platform.app.InstrumentationRegistry; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see Testing documentation 15 | */ 16 | @RunWith(AndroidJUnit4.class) 17 | public class ExampleInstrumentedTest { 18 | 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 23 | 24 | assertEquals("com.getcapacitor.android", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import android.content.Context; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | import androidx.test.platform.app.InstrumentationRegistry; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see Testing documentation 15 | */ 16 | @RunWith(AndroidJUnit4.class) 17 | public class ExampleInstrumentedTest { 18 | 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 23 | 24 | assertEquals("com.getcapacitor.app", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "QonversionCapacitor", 6 | platforms: [.iOS(.v13)], 7 | products: [ 8 | .library( 9 | name: "QonversionCapacitor", 10 | targets: ["QonversionPlugin"]) 11 | ], 12 | dependencies: [ 13 | .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", branch: "main") 14 | ], 15 | targets: [ 16 | .target( 17 | name: "QonversionPlugin", 18 | dependencies: [ 19 | .product(name: "Capacitor", package: "capacitor-swift-pm"), 20 | .product(name: "Cordova", package: "capacitor-swift-pm") 21 | ], 22 | path: "ios/Sources/QonversionPlugin"), 23 | .testTarget( 24 | name: "QonversionPluginTests", 25 | dependencies: ["QonversionPlugin"], 26 | path: "ios/Tests/QonversionPluginTests") 27 | ] 28 | ) -------------------------------------------------------------------------------- /example/ios/App/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers' 2 | 3 | platform :ios, '14.0' 4 | use_frameworks! 5 | 6 | # workaround to avoid Xcode caching of Pods that requires 7 | # Product -> Clean Build Folder after new Cordova plugins installed 8 | # Requires CocoaPods 1.6 or newer 9 | install! 'cocoapods', :disable_input_output_paths => true 10 | 11 | def capacitor_pods 12 | pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' 13 | pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' 14 | pod 'CapacitorCamera', :path => '../../node_modules/@capacitor/camera' 15 | pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen' 16 | pod 'QonversionCapacitorPlugin', :path => '../../node_modules/@qonversion/capacitor-plugin' 17 | end 18 | 19 | target 'App' do 20 | capacitor_pods 21 | # Add your Pods here 22 | end 23 | 24 | post_install do |installer| 25 | assertDeploymentTarget(installer) 26 | end 27 | -------------------------------------------------------------------------------- /src/dto/storeProducts/ProductInstallmentPlanDetails.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This class represents the details about the installment plan for a subscription product. 3 | */ 4 | export class ProductInstallmentPlanDetails { 5 | /** 6 | * Committed payments count after a user signs up for this subscription plan. 7 | */ 8 | commitmentPaymentsCount: number; 9 | 10 | /** 11 | * Subsequent committed payments count after this subscription plan renews. 12 | * 13 | * Returns 0 if the installment plan doesn't have any subsequent commitment, 14 | * which means this subscription plan will fall back to a normal 15 | * non-installment monthly plan when the plan renews. 16 | */ 17 | subsequentCommitmentPaymentsCount: number; 18 | 19 | constructor( 20 | commitmentPaymentsCount: number, 21 | subsequentCommitmentPaymentsCount: number 22 | ) { 23 | this.commitmentPaymentsCount = commitmentPaymentsCount; 24 | this.subsequentCommitmentPaymentsCount = subsequentCommitmentPaymentsCount; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "capacitor-app", 3 | "version": "1.0.0", 4 | "description": "An Amazing Capacitor App", 5 | "type": "module", 6 | "keywords": [ 7 | "capacitor", 8 | "mobile" 9 | ], 10 | "scripts": { 11 | "start": "vite", 12 | "build": "vite build", 13 | "preview": "vite preview", 14 | "sync": "npx cap sync", 15 | "ios": "npx cap run ios", 16 | "android": "npx cap run android", 17 | "clean": "cd .. && yarn build && cd example && rm -rf node_modules && yarn && yarn sync && yarn build", 18 | "refresh": "cd .. && yarn build && cd example && yarn clean" 19 | }, 20 | "dependencies": { 21 | "@capacitor/android": "^7.2.0", 22 | "@capacitor/camera": "^7.0.1", 23 | "@capacitor/core": "^7.2.0", 24 | "@capacitor/ios": "^7.2.0", 25 | "@capacitor/splash-screen": "^7.0.1", 26 | "@qonversion/capacitor-plugin": "file:.." 27 | }, 28 | "devDependencies": { 29 | "@capacitor/cli": "^7.2.0", 30 | "vite": "^5.4.2" 31 | }, 32 | "author": "", 33 | "license": "ISC" 34 | } 35 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | -------------------------------------------------------------------------------- /example/ios/App/App/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node modules 2 | node_modules/ 3 | package-lock.json 4 | yarn.lock 5 | 6 | # Build output 7 | www/ 8 | dist/ 9 | build/ 10 | .build/ 11 | out/ 12 | lib/ 13 | coverage/ 14 | 15 | # Temporary files 16 | *.tmp 17 | *.temp 18 | *.log 19 | 20 | # IDE specific files 21 | .idea/ 22 | .vscode/ 23 | *.sublime-workspace 24 | *.sublime-project 25 | 26 | # System files 27 | .DS_Store 28 | Thumbs.db 29 | *.swp 30 | *.swo 31 | 32 | # Xcode 33 | build/ 34 | *.pbxuser 35 | !default.pbxuser 36 | *.mode1v3 37 | !default.mode1v3 38 | *.mode2v3 39 | !default.mode2v3 40 | *.perspectivev3 41 | !default.perspectivev3 42 | xcuserdata 43 | *.xccheckout 44 | *.moved-aside 45 | DerivedData 46 | *.hmap 47 | *.ipa 48 | *.xcuserstate 49 | project.xcworkspace 50 | 51 | # Android/IntelliJ 52 | build/ 53 | .idea 54 | .gradle 55 | local.properties 56 | *.iml 57 | 58 | # Capacitor specific 59 | android/capacitor-plugins.json 60 | ios/Podfile.lock 61 | ios/Pods/ 62 | ios/*.xcuserdata/ 63 | ios/*.xcworkspace/ 64 | 65 | # Configuration files 66 | *.env 67 | *.local 68 | .env.local 69 | *.pid 70 | 71 | # Miscellaneous 72 | *.bak 73 | *.orig 74 | *.swp 75 | *.swo 76 | -------------------------------------------------------------------------------- /src/dto/storeProducts/ProductPrice.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Information about the Google product's price. 3 | */ 4 | export class ProductPrice { 5 | /** 6 | * Total amount of money in micro-units, 7 | * where 1,000,000 micro-units equal one unit of the currency. 8 | */ 9 | priceAmountMicros: number; 10 | 11 | /** 12 | * ISO 4217 currency code for price. 13 | */ 14 | priceCurrencyCode: string; 15 | 16 | /** 17 | * Formatted price for the payment, including its currency sign. 18 | */ 19 | formattedPrice: string; 20 | 21 | /** 22 | * True, if the price is zero. False otherwise. 23 | */ 24 | isFree: boolean; 25 | 26 | /** 27 | * Price currency symbol. Null if failed to parse. 28 | */ 29 | currencySymbol: string | null; 30 | 31 | constructor( 32 | priceAmountMicros: number, 33 | priceCurrencyCode: string, 34 | formattedPrice: string, 35 | isFree: boolean, 36 | currencySymbol: string | null = null 37 | ) { 38 | this.priceAmountMicros = priceAmountMicros; 39 | this.priceCurrencyCode = priceCurrencyCode; 40 | this.formattedPrice = formattedPrice; 41 | this.isFree = isFree; 42 | this.currencySymbol = currencySymbol; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/dto/PurchaseOptions.ts: -------------------------------------------------------------------------------- 1 | import {Product} from "./Product"; 2 | import {PurchaseUpdatePolicy} from "./enums"; 3 | import {PromotionalOffer} from './PromotionalOffer'; 4 | 5 | export class PurchaseOptions { 6 | public readonly offerId: string | null; 7 | public readonly applyOffer: boolean; 8 | public readonly oldProduct: Product | null; 9 | public readonly updatePolicy: PurchaseUpdatePolicy | null; 10 | public readonly contextKeys: string[] | null; 11 | public readonly quantity: number; 12 | public readonly promotionalOffer: PromotionalOffer | null; 13 | 14 | constructor ( 15 | offerId: string | null, 16 | applyOffer: boolean, 17 | oldProduct: Product | null, 18 | updatePolicy: PurchaseUpdatePolicy | null, 19 | contextKeys: string[] | null, 20 | quantity: number, 21 | promotionalOffer: PromotionalOffer | null 22 | ) { 23 | this.offerId = offerId; 24 | this.applyOffer = applyOffer; 25 | this.oldProduct = oldProduct; 26 | this.updatePolicy = updatePolicy; 27 | this.contextKeys = contextKeys; 28 | this.quantity = quantity; 29 | this.promotionalOffer = promotionalOffer; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ---- 3 | 4 | # Installation 5 | 6 | Make sure you have the latest version of the Xcode command line tools installed: 7 | 8 | ```sh 9 | xcode-select --install 10 | ``` 11 | 12 | For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) 13 | 14 | # Available Actions 15 | 16 | ### patch 17 | 18 | ```sh 19 | [bundle exec] fastlane patch 20 | ``` 21 | 22 | 23 | 24 | ### minor 25 | 26 | ```sh 27 | [bundle exec] fastlane minor 28 | ``` 29 | 30 | 31 | 32 | ### bump 33 | 34 | ```sh 35 | [bundle exec] fastlane bump 36 | ``` 37 | 38 | 39 | 40 | ### upgrade_sandwich 41 | 42 | ```sh 43 | [bundle exec] fastlane upgrade_sandwich 44 | ``` 45 | 46 | 47 | 48 | ### provide_next_patch_version 49 | 50 | ```sh 51 | [bundle exec] fastlane provide_next_patch_version 52 | ``` 53 | 54 | 55 | 56 | ---- 57 | 58 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. 59 | 60 | More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). 61 | 62 | The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 63 | -------------------------------------------------------------------------------- /src/dto/storeProducts/SKProductDiscount.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SKProductDiscountPaymentModes, 3 | SKProductDiscountTypes, 4 | } from "../enums"; 5 | import {SKSubscriptionPeriod} from "./SKSubscriptionPeriod"; 6 | 7 | export class SKProductDiscount { 8 | price: string; 9 | localeIdentifier?: string; 10 | numberOfPeriods: number; 11 | subscriptionPeriod?: SKSubscriptionPeriod; 12 | paymentMode: SKProductDiscountPaymentModes; 13 | identifier?: string; 14 | type: SKProductDiscountTypes; 15 | currencySymbol: string; 16 | 17 | constructor( 18 | price: string, 19 | localeIdentifier: string | undefined, 20 | numberOfPeriods: number, 21 | subscriptionPeriod: SKSubscriptionPeriod | undefined, 22 | paymentMode: SKProductDiscountPaymentModes, 23 | identifier: string | undefined, 24 | type: SKProductDiscountTypes, 25 | currencySymbol: string 26 | ) { 27 | this.price = price; 28 | this.localeIdentifier = localeIdentifier; 29 | this.numberOfPeriods = numberOfPeriods; 30 | this.subscriptionPeriod = subscriptionPeriod; 31 | this.paymentMode = paymentMode; 32 | this.identifier = identifier; 33 | this.type = type; 34 | this.currencySymbol = currencySymbol; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/QonversionConfig.ts: -------------------------------------------------------------------------------- 1 | import {EntitlementsCacheLifetime, Environment, LaunchMode} from './dto/enums'; 2 | import {EntitlementsUpdateListener} from './dto/EntitlementsUpdateListener'; 3 | 4 | export class QonversionConfig { 5 | readonly projectKey: string; 6 | readonly launchMode: LaunchMode; 7 | readonly environment: Environment; 8 | readonly entitlementsCacheLifetime: EntitlementsCacheLifetime; 9 | readonly entitlementsUpdateListener: EntitlementsUpdateListener | undefined; 10 | readonly proxyUrl: string | undefined; 11 | readonly kidsMode: boolean; 12 | 13 | constructor( 14 | projectKey: string, 15 | launchMode: LaunchMode, 16 | environment: Environment = Environment.PRODUCTION, 17 | entitlementsCacheLifetime: EntitlementsCacheLifetime = EntitlementsCacheLifetime.MONTH, 18 | entitlementsUpdateListener: EntitlementsUpdateListener | undefined = undefined, 19 | proxyUrl: string | undefined, 20 | kidsMode: boolean = false 21 | ) { 22 | this.projectKey = projectKey; 23 | this.launchMode = launchMode; 24 | this.environment = environment; 25 | this.entitlementsCacheLifetime = entitlementsCacheLifetime; 26 | this.entitlementsUpdateListener = entitlementsUpdateListener; 27 | this.proxyUrl = proxyUrl; 28 | this.kidsMode = kidsMode; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/dto/AutomationsDelegate.ts: -------------------------------------------------------------------------------- 1 | import ActionResult from "./ActionResult"; 2 | 3 | export interface AutomationsDelegate { 4 | 5 | /** 6 | * Called when Automations' screen is shown. 7 | * @param screenId shown screen id. 8 | */ 9 | automationsDidShowScreen(screenId: string): void; 10 | 11 | /** 12 | * Called when Automations flow starts executing an action. 13 | * @param actionResult action that is being executed. 14 | */ 15 | automationsDidStartExecuting(actionResult: ActionResult): void; 16 | 17 | /** 18 | * Called when Automations flow fails executing an action. 19 | * @param actionResult failed action. 20 | */ 21 | automationsDidFailExecuting(actionResult: ActionResult): void; 22 | 23 | /** 24 | * Called when Automations flow finishes executing an action. 25 | * @param actionResult executed action. 26 | * For instance, if the user made a purchase then action.type = ActionResultType.purchase. 27 | * You can use the {@link QonversionApi.checkEntitlements} method to get available permissions. 28 | */ 29 | automationsDidFinishExecuting(actionResult: ActionResult): void; 30 | 31 | /** 32 | * Called when Automations flow is finished and the Automations screen is closed 33 | */ 34 | automationsFinished(): void; 35 | } 36 | -------------------------------------------------------------------------------- /src/dto/Transaction.ts: -------------------------------------------------------------------------------- 1 | import {TransactionEnvironment, TransactionOwnershipType, TransactionType} from './enums'; 2 | 3 | export class Transaction { 4 | originalTransactionId: string; 5 | transactionId: string; 6 | transactionDate: Date; 7 | environment: TransactionEnvironment; 8 | ownershipType: TransactionOwnershipType; 9 | type: TransactionType; 10 | expirationDate?: Date; 11 | transactionRevocationDate?: Date; 12 | offerCode?: string; 13 | promoOfferId?: string; 14 | 15 | constructor( 16 | originalTransactionId: string, 17 | transactionId: string, 18 | transactionTimestamp: number, 19 | environment: TransactionEnvironment, 20 | ownershipType: TransactionOwnershipType, 21 | type: TransactionType, 22 | expirationTimestamp: number | undefined, 23 | transactionRevocationTimestamp: number | undefined, 24 | offerCode: string | undefined, 25 | promoOfferId: string | undefined, 26 | ) { 27 | this.originalTransactionId = originalTransactionId; 28 | this.transactionId = transactionId; 29 | this.transactionDate = new Date(transactionTimestamp); 30 | this.environment = environment; 31 | this.ownershipType = ownershipType; 32 | this.type = type; 33 | this.expirationDate = expirationTimestamp ? new Date(expirationTimestamp) : undefined; 34 | this.transactionRevocationDate = transactionRevocationTimestamp ? new Date(transactionRevocationTimestamp) : undefined; 35 | this.offerCode = offerCode; 36 | this.promoOfferId = promoOfferId; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export {default as Qonversion} from './Qonversion'; 2 | export * from './dto/enums'; 3 | export * from './dto/Entitlement'; 4 | export * from './dto/IntroEligibility'; 5 | export * from './dto/Offering'; 6 | export * from './dto/Offerings'; 7 | export * from './dto/Product'; 8 | export * from './dto/PromotionalOffer'; 9 | export * from './dto/QonversionError'; 10 | export * from './dto/User'; 11 | export * from './dto/UserProperty'; 12 | export * from './dto/UserProperties'; 13 | export * from './QonversionConfig'; 14 | export * from './QonversionConfigBuilder'; 15 | export * from './dto/Experiment'; 16 | export * from './dto/Transaction'; 17 | export * from './dto/RemoteConfig'; 18 | export * from './dto/RemoteConfigList'; 19 | export * from './dto/RemoteConfigurationSource'; 20 | export * from './dto/ExperimentGroup'; 21 | export * from './dto/SubscriptionPeriod'; 22 | export * from './dto/storeProducts/ProductInAppDetails'; 23 | export * from './dto/storeProducts/ProductInstallmentPlanDetails'; 24 | export * from './dto/storeProducts/ProductOfferDetails'; 25 | export * from './dto/storeProducts/ProductPrice'; 26 | export * from './dto/storeProducts/ProductPricingPhase'; 27 | export * from './dto/storeProducts/ProductStoreDetails'; 28 | export * from './dto/PurchaseOptions'; 29 | export * from './dto/PurchaseOptionsBuilder'; 30 | export * from './dto/EntitlementsUpdateListener'; 31 | export * from './dto/PromoPurchasesListener'; 32 | export * from './dto/storeProducts/SKPaymentDiscount'; 33 | export * from './dto/storeProducts/SKProduct'; 34 | export * from './dto/storeProducts/SKProductDiscount'; 35 | export * from './dto/storeProducts/SKSubscriptionPeriod'; 36 | export * from './dto/storeProducts/SkuDetails'; 37 | export * from './dto/ActionResult'; 38 | -------------------------------------------------------------------------------- /src/Qonversion.ts: -------------------------------------------------------------------------------- 1 | import {QonversionConfig} from './QonversionConfig'; 2 | import QonversionInternal from './internal/QonversionInternal'; 3 | import {QonversionApi} from './QonversionApi'; 4 | 5 | export default class Qonversion { 6 | private constructor() {} 7 | 8 | private static backingInstance: QonversionApi | undefined; 9 | 10 | /** 11 | * Use this variable to get a current initialized instance of the Qonversion SDK. 12 | * Please, use the property only after calling {@link Qonversion.initialize}. 13 | * Otherwise, trying to access the variable will cause an exception. 14 | * 15 | * @return Current initialized instance of the Qonversion SDK. 16 | * @throws error if the instance has not been initialized 17 | */ 18 | static getSharedInstance(): QonversionApi { 19 | if (!this.backingInstance) { 20 | throw "Qonversion has not been initialized. You should call " + 21 | "the initialize method before accessing the shared instance of Qonversion." 22 | } 23 | 24 | return this.backingInstance; 25 | } 26 | 27 | /** 28 | * An entry point to use Qonversion SDK. Call to initialize Qonversion SDK with required and extra configs. 29 | * The function is the best way to set additional configs you need to use Qonversion SDK. 30 | * You still have an option to set a part of additional configs later via calling separate setters. 31 | * 32 | * @param config a config that contains key SDK settings. 33 | * Call {@link QonversionConfigBuilder.build} to configure and create a QonversionConfig instance. 34 | * @return Initialized instance of the Qonversion SDK. 35 | */ 36 | static initialize(config: QonversionConfig): QonversionApi { 37 | this.backingInstance = new QonversionInternal(config); 38 | return this.backingInstance; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/ios/App/App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UIViewControllerBasedStatusBarAppearance 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/ios/App/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Capacitor (7.3.0): 3 | - CapacitorCordova 4 | - CapacitorCamera (7.0.1): 5 | - Capacitor 6 | - CapacitorCordova (7.3.0) 7 | - CapacitorSplashScreen (7.0.1): 8 | - Capacitor 9 | - Qonversion (5.13.4): 10 | - Qonversion/Main (= 5.13.4) 11 | - Qonversion/Main (5.13.4) 12 | - QonversionCapacitorPlugin (0.3.1): 13 | - Capacitor 14 | - QonversionSandwich (= 5.2.1) 15 | - QonversionSandwich (5.2.1): 16 | - Qonversion (= 5.13.4) 17 | 18 | DEPENDENCIES: 19 | - "Capacitor (from `../../node_modules/@capacitor/ios`)" 20 | - "CapacitorCamera (from `../../node_modules/@capacitor/camera`)" 21 | - "CapacitorCordova (from `../../node_modules/@capacitor/ios`)" 22 | - "CapacitorSplashScreen (from `../../node_modules/@capacitor/splash-screen`)" 23 | - "QonversionCapacitorPlugin (from `../../node_modules/@qonversion/capacitor-plugin`)" 24 | 25 | SPEC REPOS: 26 | trunk: 27 | - Qonversion 28 | - QonversionSandwich 29 | 30 | EXTERNAL SOURCES: 31 | Capacitor: 32 | :path: "../../node_modules/@capacitor/ios" 33 | CapacitorCamera: 34 | :path: "../../node_modules/@capacitor/camera" 35 | CapacitorCordova: 36 | :path: "../../node_modules/@capacitor/ios" 37 | CapacitorSplashScreen: 38 | :path: "../../node_modules/@capacitor/splash-screen" 39 | QonversionCapacitorPlugin: 40 | :path: "../../node_modules/@qonversion/capacitor-plugin" 41 | 42 | SPEC CHECKSUMS: 43 | Capacitor: fbd134fa28e503720559ecddb5ab6b41d69de347 44 | CapacitorCamera: eb8687d8687fed853598ec9460d94bcd5e16babe 45 | CapacitorCordova: 2685f5c43675793b5f06dfd66b3b26268f003b97 46 | CapacitorSplashScreen: 19cd3573e57507e02d6f34597a8c421e00931487 47 | Qonversion: 40c22c4da4aa07f999115f6bf7ce4abaf3f4a90b 48 | QonversionCapacitorPlugin: 1d42e7b6fa204f8c7614065b704cd258bde5ebb6 49 | QonversionSandwich: 36e19e9c7c2016b2b12ade6660fa6d0b25539775 50 | 51 | PODFILE CHECKSUM: afe3f9454c98dc22ff3619593d5dcbd43e1499e8 52 | 53 | COCOAPODS: 1.15.2 54 | -------------------------------------------------------------------------------- /src/dto/storeProducts/ProductPricingPhase.ts: -------------------------------------------------------------------------------- 1 | import {SubscriptionPeriod} from '../SubscriptionPeriod'; 2 | import {ProductPrice} from './ProductPrice'; 3 | import {PricingPhaseRecurrenceMode, PricingPhaseType} from '../enums'; 4 | 5 | /** 6 | * This class represents a pricing phase, describing how a user pays at a point in time. 7 | */ 8 | export class ProductPricingPhase { 9 | /** 10 | * Price for the current phase. 11 | */ 12 | price: ProductPrice; 13 | 14 | /** 15 | * The billing period for which the given price applies. 16 | */ 17 | billingPeriod: SubscriptionPeriod; 18 | 19 | /** 20 | * Number of cycles for which the billing period is applied. 21 | */ 22 | billingCycleCount: number; 23 | 24 | /** 25 | * Recurrence mode for the pricing phase. 26 | */ 27 | recurrenceMode: PricingPhaseRecurrenceMode; 28 | 29 | /** 30 | * Type of the pricing phase. 31 | */ 32 | type: PricingPhaseType; 33 | 34 | /** 35 | * True, if the current phase is a trial period. False otherwise. 36 | */ 37 | isTrial: boolean; 38 | 39 | /** 40 | * True, if the current phase is an intro period. False otherwise. 41 | * The intro phase is one of single or recurrent discounted payments. 42 | */ 43 | isIntro: boolean; 44 | 45 | /** 46 | * True, if the current phase represents the base plan. False otherwise. 47 | */ 48 | isBasePlan: boolean; 49 | 50 | constructor( 51 | price: ProductPrice, 52 | billingPeriod: SubscriptionPeriod, 53 | billingCycleCount: number, 54 | recurrenceMode: PricingPhaseRecurrenceMode, 55 | type: PricingPhaseType, 56 | isTrial: boolean, 57 | isIntro: boolean, 58 | isBasePlan: boolean, 59 | ) { 60 | this.price = price; 61 | this.billingPeriod = billingPeriod; 62 | this.billingCycleCount = billingCycleCount; 63 | this.recurrenceMode = recurrenceMode; 64 | this.type = type; 65 | this.isTrial = isTrial; 66 | this.isIntro = isIntro; 67 | this.isBasePlan = isBasePlan; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /src/dto/storeProducts/SKProduct.ts: -------------------------------------------------------------------------------- 1 | import {SKProductDiscount} from "./SKProductDiscount"; 2 | import {SKSubscriptionPeriod} from "./SKSubscriptionPeriod"; 3 | 4 | export class SKProduct { 5 | localizedDescription?: string; 6 | localizedTitle?: string; 7 | price: string; 8 | localeIdentifier?: string; 9 | productIdentifier?: string; 10 | isDownloadable: boolean; 11 | downloadContentVersion?: string; 12 | downloadContentLengths?: Array; 13 | subscriptionPeriod?: SKSubscriptionPeriod; 14 | productDiscount?: SKProductDiscount; 15 | discounts?: Array; 16 | subscriptionGroupIdentifier?: string; 17 | isFamilyShareable?: boolean; 18 | currencyCode: string; 19 | 20 | constructor( 21 | localizedDescription: string | undefined, 22 | localizedTitle: string | undefined, 23 | price: string, 24 | localeIdentifier: string | undefined, 25 | productIdentifier: string | undefined, 26 | isDownloadable: boolean, 27 | downloadContentVersion: string | undefined, 28 | downloadContentLengths: number[] | undefined, 29 | subscriptionPeriod: SKSubscriptionPeriod | undefined, 30 | productDiscount: SKProductDiscount | undefined, 31 | discounts: SKProductDiscount[] | undefined, 32 | subscriptionGroupIdentifier: string | undefined, 33 | isFamilyShareable: boolean | undefined, 34 | currencyCode: string 35 | ) { 36 | this.localizedDescription = localizedDescription; 37 | this.localizedTitle = localizedTitle; 38 | this.price = price; 39 | this.localeIdentifier = localeIdentifier; 40 | this.productIdentifier = productIdentifier; 41 | this.isDownloadable = isDownloadable; 42 | this.downloadContentVersion = downloadContentVersion; 43 | this.downloadContentLengths = downloadContentLengths; 44 | this.subscriptionPeriod = subscriptionPeriod; 45 | this.productDiscount = productDiscount; 46 | this.discounts = discounts; 47 | this.subscriptionGroupIdentifier = subscriptionGroupIdentifier; 48 | this.isFamilyShareable = isFamilyShareable; 49 | this.currencyCode = currencyCode; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /example/ios/App/App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /android/src/main/java/io/qonversion/capacitor/extenstions.kt: -------------------------------------------------------------------------------- 1 | package io.qonversion.capacitor 2 | 3 | import com.getcapacitor.JSObject 4 | import com.getcapacitor.PluginCall 5 | import io.qonversion.sandwich.BridgeData 6 | import io.qonversion.sandwich.ResultListener 7 | import io.qonversion.sandwich.SandwichError 8 | import org.json.JSONArray 9 | import org.json.JSONObject 10 | 11 | internal fun BridgeData.toJSObject(): JSObject { 12 | val json = JSONObject(this) 13 | return JSObject.fromJSONObject(json) 14 | } 15 | 16 | internal fun PluginCall.toResultListener(): ResultListener { 17 | return object : ResultListener { 18 | override fun onError(error: SandwichError) { 19 | sandwichError(error) 20 | } 21 | 22 | override fun onSuccess(data: BridgeData) { 23 | resolve(data.toJSObject()) 24 | } 25 | } 26 | } 27 | 28 | internal fun PluginCall.noNecessaryDataError(argument: String) { 29 | reject("Could not find necessary arguments. Make sure you pass correct value for the argument \"$argument\"", "NoNecessaryDataError") 30 | } 31 | 32 | internal fun PluginCall.sandwichError(error: SandwichError) { 33 | reject(error.description + ". " + error.additionalMessage, error.code) 34 | } 35 | 36 | internal fun JSONObject.toMap(): Map { 37 | val map: MutableMap = HashMap() 38 | val keys: Iterator = keys() 39 | while (keys.hasNext()) { 40 | val key = keys.next() 41 | var value = get(key) 42 | if (value is JSONArray) { 43 | value = value.toList() 44 | } else if (value is JSObject) { 45 | value = value.toMap() 46 | } 47 | map[key] = value 48 | } 49 | return map 50 | } 51 | 52 | internal fun JSONArray.toList(): List { 53 | val list: MutableList = ArrayList() 54 | for (i in 0 until length()) { 55 | var value = get(i) 56 | if (value is JSONArray) { 57 | value = value.toList() 58 | } else if (value is JSObject) { 59 | value = value.toMap() 60 | } 61 | list.add(value) 62 | } 63 | return list 64 | } -------------------------------------------------------------------------------- /src/dto/storeProducts/SkuDetails.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | */ 4 | export class SkuDetails { 5 | description: string; 6 | freeTrialPeriod: string; 7 | iconUrl: string; 8 | introductoryPrice: string; 9 | introductoryPriceAmountMicros: number; 10 | introductoryPriceCycles: number; 11 | introductoryPricePeriod: string; 12 | originalJson: string; 13 | originalPrice: string; 14 | originalPriceAmountMicros: number; 15 | price: string; 16 | priceAmountMicros: number; 17 | priceCurrencyCode: string; 18 | sku: string; 19 | subscriptionPeriod: string; 20 | title: string; 21 | type: string; 22 | hashCode: number; 23 | toString: string; 24 | 25 | constructor( 26 | description: string, 27 | freeTrialPeriod: string, 28 | iconUrl: string, 29 | introductoryPrice: string, 30 | introductoryPriceAmountMicros: number, 31 | introductoryPriceCycles: number, 32 | introductoryPricePeriod: string, 33 | originalJson: string, 34 | originalPrice: string, 35 | originalPriceAmountMicros: number, 36 | price: string, 37 | priceAmountMicros: number, 38 | priceCurrencyCode: string, 39 | sku: string, 40 | subscriptionPeriod: string, 41 | title: string, 42 | type: string, 43 | hashCode: number, 44 | toString: string 45 | ) { 46 | this.description = description; 47 | this.freeTrialPeriod = freeTrialPeriod; 48 | this.iconUrl = iconUrl; 49 | this.introductoryPrice = introductoryPrice; 50 | this.introductoryPriceAmountMicros = introductoryPriceAmountMicros; 51 | this.introductoryPriceCycles = introductoryPriceCycles; 52 | this.introductoryPricePeriod = introductoryPricePeriod; 53 | this.originalJson = originalJson; 54 | this.originalPrice = originalPrice; 55 | this.originalPriceAmountMicros = originalPriceAmountMicros; 56 | this.price = price; 57 | this.priceAmountMicros = priceAmountMicros; 58 | this.priceCurrencyCode = priceCurrencyCode; 59 | this.sku = sku; 60 | this.subscriptionPeriod = subscriptionPeriod; 61 | this.title = title; 62 | this.type = type; 63 | this.hashCode = hashCode; 64 | this.toString = toString; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/dto/Entitlement.ts: -------------------------------------------------------------------------------- 1 | import {EntitlementSource, EntitlementRenewState, EntitlementGrantType} from './enums'; 2 | import {Transaction} from './Transaction'; 3 | 4 | export class Entitlement { 5 | id: string; 6 | productId: string; 7 | isActive: boolean; 8 | renewState: EntitlementRenewState; 9 | source: EntitlementSource; 10 | startedDate: Date; 11 | renewsCount: number; 12 | grantType: EntitlementGrantType; 13 | transactions: Array; 14 | expirationDate?: Date; 15 | trialStartDate?: Date; 16 | firstPurchaseDate?: Date; 17 | lastPurchaseDate?: Date; 18 | autoRenewDisableDate?: Date; 19 | lastActivatedOfferCode?: string; 20 | 21 | constructor( 22 | id: string, 23 | productId: string, 24 | isActive: boolean, 25 | renewState: EntitlementRenewState, 26 | source: EntitlementSource, 27 | startedTimestamp: number, 28 | renewsCount: number, 29 | grantType: EntitlementGrantType, 30 | transactions: Array, 31 | expirationTimestamp: number | undefined, 32 | trialStartTimestamp: number | undefined, 33 | firstPurchaseTimestamp: number | undefined, 34 | lastPurchaseTimestamp: number | undefined, 35 | autoRenewDisableTimestamp: number | undefined, 36 | lastActivatedOfferCode: string | undefined, 37 | ) { 38 | this.id = id; 39 | this.productId = productId; 40 | this.isActive = isActive; 41 | this.renewState = renewState; 42 | this.source = source; 43 | this.startedDate = new Date(startedTimestamp); 44 | this.expirationDate = expirationTimestamp ? new Date(expirationTimestamp) : undefined; 45 | this.renewsCount = renewsCount; 46 | this.grantType = grantType; 47 | this.transactions = transactions; 48 | this.expirationDate = expirationTimestamp ? new Date(expirationTimestamp) : undefined; 49 | this.trialStartDate = trialStartTimestamp ? new Date(trialStartTimestamp) : undefined; 50 | this.firstPurchaseDate = firstPurchaseTimestamp ? new Date(firstPurchaseTimestamp) : undefined; 51 | this.lastPurchaseDate = lastPurchaseTimestamp ? new Date(lastPurchaseTimestamp) : undefined; 52 | this.autoRenewDisableDate = autoRenewDisableTimestamp ? new Date(autoRenewDisableTimestamp) : undefined; 53 | this.lastActivatedOfferCode = lastActivatedOfferCode; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | namespace "com.qonversion.sample" 5 | compileSdk rootProject.ext.compileSdkVersion 6 | defaultConfig { 7 | applicationId "com.qonversion.sample" 8 | minSdkVersion rootProject.ext.minSdkVersion 9 | targetSdkVersion rootProject.ext.targetSdkVersion 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | aaptOptions { 14 | // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. 15 | // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 16 | ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' 17 | } 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | repositories { 28 | flatDir{ 29 | dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' 30 | } 31 | } 32 | 33 | dependencies { 34 | implementation fileTree(include: ['*.jar'], dir: 'libs') 35 | implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" 36 | implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" 37 | implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion" 38 | implementation project(':capacitor-android') 39 | testImplementation "junit:junit:$junitVersion" 40 | androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" 41 | androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" 42 | implementation project(':capacitor-cordova-android-plugins') 43 | } 44 | 45 | apply from: 'capacitor.build.gradle' 46 | 47 | try { 48 | def servicesJSON = file('google-services.json') 49 | if (servicesJSON.text) { 50 | apply plugin: 'com.google.gms.google-services' 51 | } 52 | } catch(Exception e) { 53 | logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work") 54 | } 55 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | # Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore 2 | 3 | # Built application files 4 | *.apk 5 | *.aar 6 | *.ap_ 7 | *.aab 8 | 9 | # Files for the ART/Dalvik VM 10 | *.dex 11 | 12 | # Java class files 13 | *.class 14 | 15 | # Generated files 16 | bin/ 17 | gen/ 18 | out/ 19 | # Uncomment the following line in case you need and you don't have the release build type files in your app 20 | # release/ 21 | 22 | # Gradle files 23 | .gradle/ 24 | build/ 25 | 26 | # Local configuration file (sdk path, etc) 27 | local.properties 28 | 29 | # Proguard folder generated by Eclipse 30 | proguard/ 31 | 32 | # Log Files 33 | *.log 34 | 35 | # Android Studio Navigation editor temp files 36 | .navigation/ 37 | 38 | # Android Studio captures folder 39 | captures/ 40 | 41 | # IntelliJ 42 | *.iml 43 | .idea/workspace.xml 44 | .idea/tasks.xml 45 | .idea/gradle.xml 46 | .idea/assetWizardSettings.xml 47 | .idea/dictionaries 48 | .idea/libraries 49 | # Android Studio 3 in .gitignore file. 50 | .idea/caches 51 | .idea/modules.xml 52 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 53 | .idea/navEditor.xml 54 | 55 | # Keystore files 56 | # Uncomment the following lines if you do not want to check your keystore files in. 57 | #*.jks 58 | #*.keystore 59 | 60 | # External native build folder generated in Android Studio 2.2 and later 61 | .externalNativeBuild 62 | .cxx/ 63 | 64 | # Google Services (e.g. APIs or Firebase) 65 | # google-services.json 66 | 67 | # Freeline 68 | freeline.py 69 | freeline/ 70 | freeline_project_description.json 71 | 72 | # fastlane 73 | fastlane/report.xml 74 | fastlane/Preview.html 75 | fastlane/screenshots 76 | fastlane/test_output 77 | fastlane/readme.md 78 | 79 | # Version control 80 | vcs.xml 81 | 82 | # lint 83 | lint/intermediates/ 84 | lint/generated/ 85 | lint/outputs/ 86 | lint/tmp/ 87 | # lint/reports/ 88 | 89 | # Android Profiling 90 | *.hprof 91 | 92 | # Cordova plugins for Capacitor 93 | capacitor-cordova-android-plugins 94 | 95 | # Copied web assets 96 | app/src/main/assets/public 97 | 98 | # Generated Config files 99 | app/src/main/assets/capacitor.config.json 100 | app/src/main/assets/capacitor.plugins.json 101 | app/src/main/res/xml/config.xml 102 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2' 3 | androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.6.1' 4 | androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.5' 5 | androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.5.1' 6 | } 7 | 8 | buildscript { 9 | ext { 10 | kotlin_version = '2.0.20' 11 | } 12 | repositories { 13 | google() 14 | mavenCentral() 15 | } 16 | dependencies { 17 | classpath 'com.android.tools.build:gradle:8.2.1' 18 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | apply plugin: 'org.jetbrains.kotlin.android' 24 | 25 | android { 26 | namespace "io.qonversion.capacitor" 27 | compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34 28 | defaultConfig { 29 | minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22 30 | targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34 31 | versionCode 1 32 | versionName "1.0" 33 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 34 | } 35 | buildTypes { 36 | release { 37 | minifyEnabled false 38 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 39 | } 40 | } 41 | lintOptions { 42 | abortOnError false 43 | } 44 | compileOptions { 45 | sourceCompatibility JavaVersion.VERSION_17 46 | targetCompatibility JavaVersion.VERSION_17 47 | } 48 | kotlinOptions { 49 | jvmTarget = '17' 50 | } 51 | } 52 | 53 | repositories { 54 | google() 55 | mavenCentral() 56 | } 57 | 58 | 59 | dependencies { 60 | implementation fileTree(dir: 'libs', include: ['*.jar']) 61 | implementation project(':capacitor-android') 62 | implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" 63 | implementation 'androidx.core:core-ktx:1.13.1' 64 | implementation "io.qonversion:sandwich:5.2.1" 65 | testImplementation "junit:junit:$junitVersion" 66 | androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" 67 | androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" 68 | } 69 | -------------------------------------------------------------------------------- /src/dto/UserProperties.ts: -------------------------------------------------------------------------------- 1 | import {UserProperty} from './UserProperty'; 2 | import {UserPropertyKey} from './enums'; 3 | 4 | export class UserProperties { 5 | /** 6 | * List of all user properties. 7 | */ 8 | properties: UserProperty[]; 9 | 10 | /** 11 | * List of user properties, set for the Qonversion defined keys. 12 | * This is a subset of all {@link properties} list. 13 | * See {@link QonversionApi.setUserProperty}. 14 | */ 15 | definedProperties: UserProperty[]; 16 | 17 | /** 18 | * List of user properties, set for custom keys. 19 | * This is a subset of all {@link properties} list. 20 | * See {@link QonversionApi.setCustomUserProperty}. 21 | */ 22 | customProperties: UserProperty[]; 23 | 24 | /** 25 | * Map of all user properties. 26 | * This is a flattened version of the {@link properties} list as a key-value map. 27 | */ 28 | flatPropertiesMap: Map; 29 | 30 | /** 31 | * Map of user properties, set for the Qonversion defined keys. 32 | * This is a flattened version of the {@link definedProperties} list as a key-value map. 33 | * See {@link QonversionApi.setUserProperty}. 34 | */ 35 | flatDefinedPropertiesMap: Map; 36 | 37 | /** 38 | * Map of user properties, set for custom keys. 39 | * This is a flattened version of the {@link customProperties} list as a key-value map. 40 | * See {@link QonversionApi.setCustomUserProperty}. 41 | */ 42 | flatCustomPropertiesMap: Map; 43 | 44 | constructor(properties: UserProperty[]) { 45 | this.properties = properties; 46 | this.definedProperties = properties.filter(property => property.definedKey !== UserPropertyKey.CUSTOM); 47 | this.customProperties = properties.filter(property => property.definedKey === UserPropertyKey.CUSTOM); 48 | 49 | this.flatPropertiesMap = new Map(); 50 | this.flatDefinedPropertiesMap = new Map(); 51 | this.flatCustomPropertiesMap = new Map(); 52 | properties.forEach(property => { 53 | this.flatPropertiesMap.set(property.key, property.value); 54 | if (property.definedKey == UserPropertyKey.CUSTOM) { 55 | this.flatCustomPropertiesMap.set(property.key, property.value); 56 | } else { 57 | this.flatDefinedPropertiesMap.set(property.definedKey, property.value); 58 | } 59 | }); 60 | } 61 | 62 | /** 63 | * Searches for a property with the given property {@link key} in all properties list. 64 | */ 65 | getProperty(key: string): UserProperty | undefined { 66 | return this.properties.find(userProperty => userProperty.key == key); 67 | } 68 | 69 | /** 70 | * Searches for a property with the given Qonversion defined property {@link key} 71 | * in defined properties list. 72 | */ 73 | getDefinedProperty(key: UserPropertyKey): UserProperty | undefined { 74 | return this.definedProperties.find(userProperty => userProperty.definedKey == key); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /example/ios/App/App.xcodeproj/xcshareddata/xcschemes/App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /example/ios/App/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Capacitor 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 10 | // Override point for customization after application launch. 11 | return true 12 | } 13 | 14 | func applicationWillResignActive(_ application: UIApplication) { 15 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 16 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 17 | } 18 | 19 | func applicationDidEnterBackground(_ application: UIApplication) { 20 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 21 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 22 | } 23 | 24 | func applicationWillEnterForeground(_ application: UIApplication) { 25 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 26 | } 27 | 28 | func applicationDidBecomeActive(_ application: UIApplication) { 29 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 30 | } 31 | 32 | func applicationWillTerminate(_ application: UIApplication) { 33 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 34 | } 35 | 36 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { 37 | // Called when the app was launched with a url. Feel free to add additional processing here, 38 | // but if you want the App API to support tracking app url opens, make sure to keep this call 39 | return ApplicationDelegateProxy.shared.application(app, open: url, options: options) 40 | } 41 | 42 | func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { 43 | // Called when the app was launched with an activity, including Universal Links. 44 | // Feel free to add additional processing here, but if you want the App API to support 45 | // tracking app url opens, make sure to keep this call 46 | return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/dto/storeProducts/ProductOfferDetails.ts: -------------------------------------------------------------------------------- 1 | import {ProductPricingPhase} from "./ProductPricingPhase"; 2 | import {ProductInstallmentPlanDetails} from './ProductInstallmentPlanDetails'; 3 | 4 | /** 5 | * This class contains all the information about the Google subscription offer details. 6 | * It might be either a plain base plan details or a base plan with the concrete offer details. 7 | */ 8 | export class ProductOfferDetails { 9 | /** 10 | * The identifier of the current base plan. 11 | */ 12 | basePlanId: string; 13 | 14 | /** 15 | * The identifier of the concrete offer, to which these details belong. 16 | * Null, if these are plain base plan details. 17 | */ 18 | offerId: string | null; 19 | 20 | /** 21 | * A token to purchase the current offer. 22 | */ 23 | offerToken: string; 24 | 25 | /** 26 | * List of tags set for the current offer. 27 | */ 28 | tags: string[]; 29 | 30 | /** 31 | * A time-ordered list of pricing phases for the current offer. 32 | */ 33 | pricingPhases: ProductPricingPhase[]; 34 | 35 | /** 36 | * A base plan phase details. 37 | */ 38 | basePlan: ProductPricingPhase | null; 39 | 40 | /** 41 | * Additional details of an installment plan, if exists. 42 | */ 43 | installmentPlanDetails: ProductInstallmentPlanDetails | null; 44 | 45 | /** 46 | * A trial phase details, if exists. 47 | */ 48 | introPhase: ProductPricingPhase | null; 49 | 50 | /** 51 | * An intro phase details, if exists. 52 | * The intro phase is one of single or recurrent discounted payments. 53 | */ 54 | trialPhase: ProductPricingPhase | null; 55 | 56 | /** 57 | * True, if there is a trial phase in the current offer. False otherwise. 58 | */ 59 | hasTrial: boolean; 60 | 61 | /** 62 | * True, if there is any intro phase in the current offer. False otherwise. 63 | * The intro phase is one of single or recurrent discounted payments. 64 | */ 65 | hasIntro: boolean; 66 | 67 | /** 68 | * True, if there is any trial or intro phase in the current offer. False otherwise. 69 | * The intro phase is one of single or recurrent discounted payments. 70 | */ 71 | hasTrialOrIntro: boolean; 72 | 73 | constructor( 74 | basePlanId: string, 75 | offerId: string | null, 76 | offerToken: string, 77 | tags: string[], 78 | pricingPhases: ProductPricingPhase[], 79 | basePlan: ProductPricingPhase | null, 80 | installmentPlanDetails: ProductInstallmentPlanDetails | null, 81 | introPhase: ProductPricingPhase | null, 82 | trialPhase: ProductPricingPhase | null, 83 | hasTrial: boolean, 84 | hasIntro: boolean, 85 | hasTrialOrIntro: boolean, 86 | ) { 87 | this.basePlanId = basePlanId; 88 | this.offerId = offerId; 89 | this.offerToken = offerToken; 90 | this.tags = tags; 91 | this.pricingPhases = pricingPhases; 92 | this.basePlan = basePlan; 93 | this.installmentPlanDetails = installmentPlanDetails; 94 | this.introPhase = introPhase; 95 | this.trialPhase = trialPhase; 96 | this.hasTrial = hasTrial; 97 | this.hasIntro = hasIntro; 98 | this.hasTrialOrIntro = hasTrialOrIntro; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qonversion/capacitor-plugin", 3 | "version": "0.3.1", 4 | "description": "Qonversion provides full in-app purchases infrastructure, so you do not need to build your own server for receipt validation. Implement in-app subscriptions, validate user receipts, check subscription status, and provide access to your app features and content using our StoreKit wrapper and Google Play Billing wrapper.", 5 | "main": "dist/plugin.cjs.js", 6 | "module": "dist/esm/index.js", 7 | "types": "dist/esm/index.d.ts", 8 | "unpkg": "dist/plugin.js", 9 | "files": [ 10 | "android/src/main/", 11 | "android/build.gradle", 12 | "dist/", 13 | "ios/Sources", 14 | "ios/Tests", 15 | "Package.swift", 16 | "QonversionCapacitorPlugin.podspec" 17 | ], 18 | "author": { 19 | "name": "Qonversion", 20 | "email": "hi@qonversion.io" 21 | }, 22 | "license": "MIT", 23 | "readmeFilename": "README.md", 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/qonversion/qonversion-capacitor.git" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/qonversion/qonversion-capacitor/issues" 30 | }, 31 | "keywords": [ 32 | "capacitor", 33 | "ionic", 34 | "plugin", 35 | "native", 36 | "qonversion", 37 | "StoreKit", 38 | "SKStoreKit", 39 | "Google", 40 | "Play", 41 | "Billing", 42 | "in-app", 43 | "Purchases" 44 | ], 45 | "scripts": { 46 | "verify": "npm run verify:ios && npm run verify:android && npm run verify:web", 47 | "verify:ios": "xcodebuild -scheme QonversionCapacitor -destination generic/platform=iOS", 48 | "verify:android": "cd android && ./gradlew clean build test && cd ..", 49 | "verify:web": "npm run build", 50 | "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint", 51 | "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format", 52 | "eslint": "eslint . --ext ts", 53 | "prettier": "prettier \"**/*.{css,html,ts,js,java}\"", 54 | "swiftlint": "node-swiftlint", 55 | "build": "npm run clean && tsc && rollup -c rollup.config.js", 56 | "clean": "./node_modules/rimraf/bin.js ./dist", 57 | "watch": "tsc --watch", 58 | "prepublishOnly": "npm run build", 59 | "deploy": "npm publish" 60 | }, 61 | "devDependencies": { 62 | "@capacitor/android": "^7.2.0", 63 | "@capacitor/core": "^7.2.0", 64 | "@capacitor/docgen": "^0.3.0", 65 | "@capacitor/ios": "^7.2.0", 66 | "@ionic/eslint-config": "^0.4.0", 67 | "@ionic/prettier-config": "^1.0.1", 68 | "@ionic/swiftlint-config": "^1.1.2", 69 | "eslint": "^8.57.0", 70 | "prettier": "~2.3.0", 71 | "prettier-plugin-java": "~1.0.2", 72 | "rimraf": "^3.0.2", 73 | "rollup": "^2.32.0", 74 | "swiftlint": "^1.0.1", 75 | "typescript": "~4.1.5", 76 | "yarn": "^1.22.22" 77 | }, 78 | "peerDependencies": { 79 | "@capacitor/core": "^7.2.0" 80 | }, 81 | "prettier": "@ionic/prettier-config", 82 | "swiftlint": "@ionic/swiftlint-config", 83 | "eslintConfig": { 84 | "extends": "@ionic/eslint-config/recommended" 85 | }, 86 | "capacitor": { 87 | "ios": { 88 | "src": "ios" 89 | }, 90 | "android": { 91 | "src": "android" 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/QonversionNativePlugin.ts: -------------------------------------------------------------------------------- 1 | import { 2 | QEntitlement, 3 | QOfferings, 4 | QProduct, 5 | QPromotionalOffer, 6 | QRemoteConfig, 7 | QRemoteConfigList, 8 | QTrialIntroEligibility, 9 | QUser, 10 | QUserProperties 11 | } from './internal/Mapper'; 12 | 13 | export interface QonversionNativePlugin { 14 | initialize(params: { 15 | projectKey: string, 16 | launchMode: string, 17 | environment: string, 18 | entitlementsCacheLifetime: string, 19 | proxyUrl: string | undefined, 20 | kidsMode: boolean 21 | }): void; 22 | 23 | syncHistoricalData(): void; 24 | 25 | syncStoreKit2Purchases(): void; 26 | 27 | checkEntitlements(): Promise | null | undefined>; 28 | 29 | storeSdkInfo(params: {source: string, version: string}): void; 30 | 31 | getPromotionalOffer(params: {productId: string, discountId: string | undefined}): Promise; 32 | 33 | purchase(params: { 34 | productId: string, 35 | quantity?: number, 36 | contextKeys: string[] | null, 37 | offerId?: string | null | undefined, 38 | applyOffer?: boolean | undefined, 39 | oldProductId?: string | undefined, 40 | updatePolicyKey?: string | null | undefined, 41 | promoOffer?: Object | null, 42 | }): Promise | null | undefined>; 43 | 44 | products(): Promise | null | undefined>; 45 | 46 | offerings(): Promise; 47 | 48 | checkTrialIntroEligibility(params: {ids: string[]}): Promise; 49 | 50 | restore(): Promise | null | undefined>; 51 | 52 | syncPurchases(): void; 53 | 54 | identify(params: {userId: string}): Promise; 55 | 56 | logout(): void; 57 | 58 | userInfo(): Promise; 59 | 60 | remoteConfig(params: {contextKey: string | undefined}): Promise; 61 | 62 | remoteConfigList(params?: {contextKeys: string[], includeEmptyContextKey: boolean}): Promise; 63 | 64 | attachUserToExperiment(params: {experimentId: string, groupId: string}): Promise; 65 | 66 | detachUserFromExperiment(params: {experimentId: string}): Promise; 67 | 68 | attachUserToRemoteConfiguration(params: {remoteConfigurationId: string}): Promise; 69 | 70 | detachUserFromRemoteConfiguration(params: {remoteConfigurationId: string}): Promise; 71 | 72 | isFallbackFileAccessible(): Promise<{success: boolean}>; 73 | 74 | addAttributionData(params: {data: Object, provider: string}): void; 75 | 76 | setDefinedUserProperty(param: {property: string, value: string}): void; 77 | 78 | setCustomUserProperty(param: {property: string, value: string}): void; 79 | 80 | userProperties(): Promise; 81 | 82 | collectAdvertisingId(): void; 83 | 84 | collectAppleSearchAdsAttribution(): void; 85 | 86 | presentCodeRedemptionSheet(): void; 87 | 88 | promoPurchase(params: { productId: string }): Promise | null | undefined>; 89 | 90 | addListener(event: 'entitlementsUpdatedEvent', listener: (payload: (Record | null | undefined)) => void): void; 91 | 92 | addListener(event: 'shouldPurchasePromoProductEvent', listener: (payload: {productId: string}) => void): void; 93 | } 94 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | def update_js(new_version) 2 | path = Dir['../src/**/QonversionInternal.ts'].first 3 | regex = /const sdkVersion = ".*";/ 4 | result_value = "const sdkVersion = \"#{new_version}\";" 5 | 6 | update_file(path, regex, result_value) 7 | end 8 | 9 | def update_package(new_version) 10 | path = "../package.json" 11 | regex = /"version": ".*",/ 12 | result_value = "\"version\": \"#{new_version}\"," 13 | 14 | update_file(path, regex, result_value) 15 | end 16 | 17 | def upgrade_sandwich_android(new_version) 18 | path = "../android/build.gradle" 19 | common_part = "implementation \"io.qonversion:sandwich:" 20 | regex = /#{common_part}.*"/ 21 | result_value = "#{common_part}#{new_version}\"" 22 | 23 | update_file(path, regex, result_value) 24 | end 25 | 26 | def upgrade_sandwich_ios(new_version) 27 | path = "../QonversionCapacitorPlugin.podspec" 28 | common_part = "s.dependency \"QonversionSandwich\", \"" 29 | regex = /#{common_part}.*"/ 30 | result_value = "#{common_part}#{new_version}\"" 31 | 32 | update_file(path, regex, result_value) 33 | end 34 | 35 | def update_file(path, regex, result_value) 36 | file = File.read(path) 37 | new_content = file.gsub(regex, result_value) 38 | File.open(path, 'w') { |line| line.puts new_content } 39 | end 40 | 41 | def get_tag 42 | tag = last_git_tag() 43 | puts tag 44 | result_tag = tag.scan(%r{\d{1,2}.\d{1,2}.\d{1,3}}).first 45 | return result_tag 46 | end 47 | 48 | def calculate_minor_version(tag) 49 | major, minor, patch = parse_versions(tag) 50 | new_minor_version = minor.to_i.next.to_s 51 | new_version = major + "." + new_minor_version + "." + "0" 52 | return new_version 53 | end 54 | 55 | def calculate_patch_version(tag) 56 | major, minor, patch = parse_versions(tag) 57 | new_patch_version = patch.to_i.next.to_s 58 | new_version = major + "." + minor + "." + new_patch_version 59 | 60 | return new_version 61 | end 62 | 63 | def push_tag(tag) 64 | system("git checkout develop") 65 | system("git pull origin develop") 66 | add_git_tag(tag: tag) 67 | push_git_tags(tag: tag) 68 | end 69 | 70 | def parse_versions(tag) 71 | split_version_array = tag.split(".", 3) 72 | 73 | if split_version_array.length == 3 74 | major = split_version_array[0] 75 | minor = split_version_array[1] 76 | patch = split_version_array[2] 77 | 78 | return major, minor, patch 79 | end 80 | end 81 | 82 | lane :patch do 83 | tag = get_tag 84 | new_version = calculate_patch_version(tag) 85 | new_tag = "prerelease/" + new_version 86 | push_tag(new_tag) 87 | end 88 | 89 | lane :minor do 90 | tag = get_tag 91 | new_version = calculate_minor_version(tag) 92 | new_tag = "prerelease/" + new_version 93 | push_tag(new_tag) 94 | end 95 | 96 | lane :bump do |options| 97 | new_version = options[:version] 98 | 99 | update_js(new_version) 100 | update_package(new_version) 101 | end 102 | 103 | lane :upgrade_sandwich do |options| 104 | new_version = options[:version] 105 | 106 | upgrade_sandwich_android(new_version) 107 | upgrade_sandwich_ios(new_version) 108 | end 109 | 110 | lane :provide_next_patch_version do 111 | tag = get_tag 112 | new_version = calculate_patch_version(tag) 113 | sh("echo version=#{new_version} >> \"$GITHUB_ENV\"") 114 | end 115 | -------------------------------------------------------------------------------- /src/dto/Product.ts: -------------------------------------------------------------------------------- 1 | import {ProductType} from "./enums"; 2 | import {SKProduct} from "./storeProducts/SKProduct"; 3 | import {SkuDetails} from "./storeProducts/SkuDetails"; 4 | import {ProductStoreDetails} from "./storeProducts/ProductStoreDetails"; 5 | import {SubscriptionPeriod} from './SubscriptionPeriod'; 6 | 7 | export class Product { 8 | qonversionID: string; 9 | storeID: string | null; 10 | 11 | /** 12 | * Identifier of the base plan for Google product. 13 | */ 14 | basePlanID: string | null; 15 | 16 | /** 17 | * Google Play Store details of this product. 18 | * Android only. Null for iOS, or if the product was not found. 19 | * Doesn't take into account {@link basePlanID}. 20 | * @deprecated Consider using {@link storeDetails} instead. 21 | */ 22 | skuDetails: SkuDetails | null; 23 | 24 | /** 25 | * Google Play Store details of this product. 26 | * Android only. Null for iOS, or if the product was not found. 27 | */ 28 | storeDetails: ProductStoreDetails | null; 29 | 30 | /** 31 | * App store details of this product. 32 | * iOS only. Null for Android, or if the product was not found. 33 | */ 34 | skProduct: SKProduct | null; 35 | 36 | offeringId?: string | null; 37 | 38 | /** 39 | * For Android - the subscription base plan duration. If the {@link basePlanID} is not specified, 40 | * the duration is calculated using the deprecated {@link skuDetails}. 41 | * For iOS - the duration of the {@link skProduct}. 42 | * Null, if it's not a subscription product or the product was not found in the store. 43 | */ 44 | subscriptionPeriod: SubscriptionPeriod | null; 45 | 46 | /** 47 | * The subscription trial duration of the default offer for Android or of the product for iOS. 48 | * See {@link ProductStoreDetails.defaultSubscriptionOfferDetails} for the information on how we 49 | * choose the default offer for Android. 50 | * Null, if it's not a subscription product or the product was not found the store. 51 | */ 52 | trialPeriod: SubscriptionPeriod | null; 53 | 54 | /** 55 | * The calculated type of this product based on the store information. 56 | * On Android uses deprecated {@link skuDetails} for the old subscription products 57 | * where {@link basePlanID} is not specified, and {@link storeDetails} for all the other products. 58 | * On iOS uses {@link skProduct} information. 59 | */ 60 | type: ProductType; 61 | 62 | /** 63 | * Formatted price of for this product, including the currency sign. 64 | */ 65 | prettyPrice: string | null; 66 | 67 | price?: number; 68 | currencyCode?: string; 69 | storeTitle?: string; 70 | storeDescription?: string; 71 | prettyIntroductoryPrice?: string; 72 | 73 | constructor( 74 | qonversionID: string, 75 | storeID: string, 76 | basePlanID: string | null, 77 | skuDetails: SkuDetails | null, 78 | storeDetails: ProductStoreDetails | null, 79 | skProduct: SKProduct | null, 80 | offeringId: string | null, 81 | subscriptionPeriod: SubscriptionPeriod | null, 82 | trialPeriod: SubscriptionPeriod | null, 83 | type: ProductType, 84 | prettyPrice: string | null, 85 | price: number | undefined, 86 | currencyCode: string | undefined, 87 | storeTitle: string | undefined, 88 | storeDescription: string | undefined, 89 | prettyIntroductoryPrice: string | undefined, 90 | ) { 91 | this.qonversionID = qonversionID; 92 | this.storeID = storeID; 93 | this.basePlanID = basePlanID; 94 | this.skuDetails = skuDetails; 95 | this.storeDetails = storeDetails; 96 | this.skProduct = skProduct; 97 | this.offeringId = offeringId; 98 | this.subscriptionPeriod = subscriptionPeriod; 99 | this.trialPeriod = trialPeriod; 100 | this.type = type; 101 | this.prettyPrice = prettyPrice; 102 | this.price = price; 103 | this.currencyCode = currencyCode; 104 | this.storeTitle = storeTitle; 105 | this.storeDescription = storeDescription; 106 | this.prettyIntroductoryPrice = prettyIntroductoryPrice; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/QonversionConfigBuilder.ts: -------------------------------------------------------------------------------- 1 | import {EntitlementsCacheLifetime, Environment, LaunchMode} from './dto/enums'; 2 | import {EntitlementsUpdateListener} from './dto/EntitlementsUpdateListener'; 3 | import {QonversionConfig} from './QonversionConfig'; 4 | 5 | export class QonversionConfigBuilder { 6 | private readonly projectKey: string; 7 | private readonly launchMode: LaunchMode; 8 | 9 | constructor(projectKey: string, launchMode: LaunchMode) { 10 | this.projectKey = projectKey; 11 | this.launchMode = launchMode; 12 | } 13 | 14 | private environment: Environment = Environment.PRODUCTION; 15 | private entitlementsCacheLifetime: EntitlementsCacheLifetime = EntitlementsCacheLifetime.MONTH; 16 | private entitlementsUpdateListener: EntitlementsUpdateListener | undefined = undefined; 17 | private proxyUrl: string | undefined = undefined; 18 | private kidsMode: boolean = false; 19 | 20 | /** 21 | * Set current application {@link Environment}. Used to distinguish sandbox and production users. 22 | * 23 | * @param environment current environment. 24 | * @return builder instance for chain calls. 25 | */ 26 | setEnvironment(environment: Environment): QonversionConfigBuilder { 27 | this.environment = environment; 28 | return this; 29 | } 30 | 31 | /** 32 | * Entitlements cache is used when there are problems with the Qonversion API 33 | * or internet connection. If so, Qonversion will return the last successfully loaded 34 | * entitlements. The current method allows you to configure how long that cache may be used. 35 | * The default value is {@link EntitlementsCacheLifetime.MONTH}. 36 | * 37 | * @param lifetime desired entitlements cache lifetime duration 38 | * @return builder instance for chain calls. 39 | */ 40 | setEntitlementsCacheLifetime(lifetime: EntitlementsCacheLifetime): QonversionConfigBuilder { 41 | this.entitlementsCacheLifetime = lifetime; 42 | return this; 43 | } 44 | 45 | /** 46 | * Provide a listener to be notified about asynchronous user entitlements updates. 47 | * 48 | * Make sure you provide this listener for being up-to-date with the user entitlements. 49 | * Else you can lose some important updates. Also, please, consider that this listener 50 | * should live for the whole lifetime of the application. 51 | * 52 | * @param entitlementsUpdateListener listener to be called when entitlements update. 53 | * @return builder instance for chain calls. 54 | */ 55 | setEntitlementsUpdateListener(entitlementsUpdateListener: EntitlementsUpdateListener): QonversionConfigBuilder { 56 | this.entitlementsUpdateListener = entitlementsUpdateListener; 57 | return this; 58 | } 59 | 60 | /** 61 | * Provide a URL to your proxy server which will redirect all the requests from the app 62 | * to our API. Please, contact us before using this feature. 63 | * 64 | * @param url your proxy server url 65 | * @return builder instance for chain calls. 66 | * @see [The documentation](https://documentation.qonversion.io/docs/custom-proxy-server-for-sdks) 67 | */ 68 | setProxyURL(url: string): QonversionConfigBuilder { 69 | this.proxyUrl = url; 70 | return this; 71 | } 72 | 73 | /** 74 | * Android only. 75 | * Use this function to enable Qonversion SDK Kids mode. 76 | * With this mode activated, our SDK does not collect any information that violates Google Children’s Privacy Policy. 77 | * @return builder instance for chain calls. 78 | */ 79 | enableKidsMode(): QonversionConfigBuilder { 80 | this.kidsMode = true; 81 | return this; 82 | } 83 | 84 | /** 85 | * Generate {@link QonversionConfig} instance with all the provided configurations. 86 | * 87 | * @return the complete {@link QonversionConfig} instance. 88 | */ 89 | build(): QonversionConfig { 90 | return new QonversionConfig( 91 | this.projectKey, 92 | this.launchMode, 93 | this.environment, 94 | this.entitlementsCacheLifetime, 95 | this.entitlementsUpdateListener, 96 | this.proxyUrl, 97 | this.kidsMode 98 | ) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/dto/PurchaseOptionsBuilder.ts: -------------------------------------------------------------------------------- 1 | import {Product} from './Product'; 2 | import {PurchaseUpdatePolicy} from './enums'; 3 | import {ProductOfferDetails} from './storeProducts/ProductOfferDetails'; 4 | import {PurchaseOptions} from './PurchaseOptions'; 5 | import {PromotionalOffer} from './PromotionalOffer'; 6 | 7 | export class PurchaseOptionsBuilder { 8 | private offerId: string | null = null; 9 | private applyOffer: boolean = true; 10 | private oldProduct: Product | null = null; 11 | private updatePolicy: PurchaseUpdatePolicy | null = null; 12 | private contextKeys: string[] | null = null; 13 | private quantity: number = 1; 14 | private promoOffer: PromotionalOffer | null = null; 15 | 16 | /** 17 | * iOS only. 18 | * Set quantity of product purchasing. Use for consumable in-app products. 19 | * @param quantity of product purchasing. 20 | * @return builder instance for chain calls. 21 | */ 22 | setQuantity(quantity: number): PurchaseOptionsBuilder { 23 | this.quantity = quantity; 24 | return this; 25 | } 26 | 27 | /** 28 | * Android only. 29 | * Set offer for the purchase. 30 | * If offer is not specified, then the default offer will be applied. To know how we choose 31 | * the default offer, see {@link ProductStoreDetails.defaultSubscriptionOfferDetails}. 32 | * @param offer concrete offer which you'd like to purchase. 33 | * @return builder instance for chain calls. 34 | */ 35 | setOffer(offer: ProductOfferDetails) { 36 | this.offerId = offer.offerId; 37 | return this; 38 | } 39 | 40 | /** 41 | * Android only. 42 | * Set the offer Id to the purchase. 43 | * If {@link offerId} is not specified, then the default offer will be applied. To know how we choose 44 | * the default offer, see {@link ProductStoreDetails.defaultSubscriptionOfferDetails}. 45 | * @param offerId concrete offer Id which you'd like to purchase. 46 | * @return builder instance for chain calls. 47 | */ 48 | setOfferId(offerId: string): PurchaseOptionsBuilder { 49 | this.offerId = offerId; 50 | return this; 51 | } 52 | 53 | /** 54 | * Android only. 55 | * Call this function to remove any intro/trial offer from the purchase (use only a bare base plan). 56 | * @return builder instance for chain calls. 57 | */ 58 | removeOffer(): PurchaseOptionsBuilder { 59 | this.applyOffer = false; 60 | return this; 61 | } 62 | 63 | /** 64 | * Android only. 65 | * Set Qonversion product from which the upgrade/downgrade will be initialized. 66 | * 67 | * @param oldProduct Qonversion product from which the upgrade/downgrade 68 | * will be initialized. 69 | * @return builder instance for chain calls. 70 | */ 71 | setOldProduct(oldProduct: Product): PurchaseOptionsBuilder { 72 | this.oldProduct = oldProduct; 73 | return this; 74 | } 75 | 76 | /** 77 | * Android only. 78 | * Set the update policy for the purchase. 79 | * If the {@link updatePolicy} is not provided, then default one 80 | * will be selected - {@link PurchaseUpdatePolicy.WITH_TIME_PRORATION}. 81 | * @param updatePolicy update policy for the purchase. 82 | * @return builder instance for chain calls. 83 | */ 84 | setUpdatePolicy(updatePolicy: PurchaseUpdatePolicy): PurchaseOptionsBuilder { 85 | this.updatePolicy = updatePolicy; 86 | return this; 87 | } 88 | 89 | /** 90 | * Set the context keys associated with a purchase. 91 | * 92 | * @param contextKeys context keys for the purchase. 93 | * @return builder instance for chain calls. 94 | */ 95 | setContextKeys(contextKeys: string[]): PurchaseOptionsBuilder { 96 | this.contextKeys = contextKeys; 97 | return this; 98 | } 99 | 100 | /** 101 | * Set the promotional offer details. 102 | * 103 | * @param promoOffer promotional offer details. 104 | * @return builder instance for chain calls. 105 | */ 106 | setPromotionalOffer(promoOffer: PromotionalOffer): PurchaseOptionsBuilder { 107 | this.promoOffer = promoOffer; 108 | return this; 109 | } 110 | 111 | /** 112 | * Generate {@link PurchaseOptions} instance with all the provided options. 113 | * @return the complete {@link PurchaseOptions} instance. 114 | */ 115 | build(): PurchaseOptions { 116 | return new PurchaseOptions( 117 | this.offerId, 118 | this.applyOffer, 119 | this.oldProduct, 120 | this.updatePolicy, 121 | this.contextKeys, 122 | this.quantity, 123 | this.promoOffer 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/dto/storeProducts/ProductStoreDetails.ts: -------------------------------------------------------------------------------- 1 | import {ProductType} from "../enums"; 2 | import {ProductOfferDetails} from "./ProductOfferDetails"; 3 | import {ProductInAppDetails} from "./ProductInAppDetails"; 4 | 5 | /** 6 | * This class contains all the information about the concrete Google product, 7 | * either subscription or in-app. In case of a subscription also determines concrete base plan. 8 | */ 9 | export class ProductStoreDetails { 10 | /** 11 | * Identifier of the base plan to which these details relate. 12 | * Null for in-app products. 13 | */ 14 | basePlanId: string | null; 15 | 16 | /** 17 | * Identifier of the subscription or the in-app product. 18 | */ 19 | productId: string; 20 | 21 | /** 22 | * Name of the subscription or the in-app product. 23 | */ 24 | name: string; 25 | 26 | /** 27 | * Title of the subscription or the in-app product. 28 | * The title includes the name of the app. 29 | */ 30 | title: string; 31 | 32 | /** 33 | * Description of the subscription or the in-app product. 34 | */ 35 | description: string; 36 | 37 | /** 38 | * Offer details for the subscription. 39 | * Offer details contain all the available variations of purchase offers, 40 | * including both base plan and eligible base plan + offer combinations 41 | * from Google Play Console for current {@link basePlanId}. 42 | * Null for in-app products. 43 | */ 44 | subscriptionOfferDetails: ProductOfferDetails[] | null; 45 | 46 | /** 47 | * The most profitable subscription offer for the client in our opinion from all the available offers. 48 | * We calculate the cheapest price for the client by comparing all the trial or intro phases 49 | * and the base plan. 50 | */ 51 | defaultSubscriptionOfferDetails: ProductOfferDetails | null; 52 | 53 | /** 54 | * Subscription offer details containing only the base plan without any offer. 55 | */ 56 | basePlanSubscriptionOfferDetails: ProductOfferDetails | null; 57 | 58 | /** 59 | * Offer details for the in-app product. 60 | * Null for subscriptions. 61 | */ 62 | inAppOfferDetails: ProductInAppDetails | null; 63 | 64 | /** 65 | * True, if there is any eligible offer with a trial 66 | * for this subscription and base plan combination. 67 | * False otherwise or for an in-app product. 68 | */ 69 | hasTrialOffer: boolean; 70 | 71 | /** 72 | * True, if there is any eligible offer with an intro price 73 | * for this subscription and base plan combination. 74 | * False otherwise or for an in-app product. 75 | */ 76 | hasIntroOffer: boolean; 77 | 78 | /** 79 | * True, if there is any eligible offer with a trial or an intro price 80 | * for this subscription and base plan combination. 81 | * False otherwise or for an in-app product. 82 | */ 83 | hasTrialOrIntroOffer: boolean; 84 | 85 | /** 86 | * The calculated type of the current product. 87 | */ 88 | productType: ProductType; 89 | 90 | /** 91 | * True, if the product type is InApp. 92 | */ 93 | isInApp: boolean; 94 | 95 | /** 96 | * True, if the product type is Subscription. 97 | */ 98 | isSubscription: boolean; 99 | 100 | /** 101 | * True, if the subscription product is prepaid, which means that users pay in advance - 102 | * they will need to make a new payment to extend their plan. 103 | */ 104 | isPrepaid: boolean; 105 | 106 | /** 107 | * True, if the subscription product is installment, which means that users commit 108 | * to pay for a specified amount of periods every month. 109 | */ 110 | isInstallment: boolean; 111 | 112 | constructor( 113 | basePlanId: string | null, 114 | productId: string, 115 | name: string, 116 | title: string, 117 | description: string, 118 | subscriptionOfferDetails: ProductOfferDetails[] | null, 119 | defaultSubscriptionOfferDetails: ProductOfferDetails | null, 120 | basePlanSubscriptionOfferDetails: ProductOfferDetails | null, 121 | inAppOfferDetails: ProductInAppDetails | null, 122 | hasTrialOffer: boolean, 123 | hasIntroOffer: boolean, 124 | hasTrialOrIntroOffer: boolean, 125 | productType: ProductType, 126 | isInApp: boolean, 127 | isSubscription: boolean, 128 | isPrepaid: boolean, 129 | isInstallment: boolean, 130 | ) { 131 | this.basePlanId = basePlanId; 132 | this.productId = productId; 133 | this.name = name; 134 | this.title = title; 135 | this.description = description; 136 | this.subscriptionOfferDetails = subscriptionOfferDetails; 137 | this.defaultSubscriptionOfferDetails = defaultSubscriptionOfferDetails; 138 | this.basePlanSubscriptionOfferDetails = basePlanSubscriptionOfferDetails; 139 | this.inAppOfferDetails = inAppOfferDetails; 140 | this.hasTrialOffer = hasTrialOffer; 141 | this.hasIntroOffer = hasIntroOffer; 142 | this.hasTrialOrIntroOffer = hasTrialOrIntroOffer; 143 | this.productType = productType; 144 | this.isInApp = isInApp; 145 | this.isSubscription = isSubscription; 146 | this.isPrepaid = isPrepaid; 147 | this.isInstallment = isInstallment; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/internal/web.ts: -------------------------------------------------------------------------------- 1 | import {WebPlugin} from '@capacitor/core'; 2 | import {QonversionNativePlugin} from '../QonversionNativePlugin'; 3 | import { 4 | QEntitlement, 5 | QOfferings, 6 | QProduct, QPromotionalOffer, 7 | QRemoteConfig, 8 | QRemoteConfigList, 9 | QTrialIntroEligibility, 10 | QUser, 11 | QUserProperties 12 | } from './Mapper'; 13 | 14 | export class QonversionWeb extends WebPlugin implements QonversionNativePlugin { 15 | addAttributionData(params: { data: Object; provider: string }): void { 16 | throw this.unimplemented("not implemented yet"); 17 | } 18 | 19 | attachUserToExperiment(params: { experimentId: string; groupId: string }): Promise { 20 | throw this.unimplemented("not implemented yet"); 21 | } 22 | 23 | attachUserToRemoteConfiguration(params: { remoteConfigurationId: string }): Promise { 24 | throw this.unimplemented("not implemented yet"); 25 | } 26 | 27 | checkEntitlements(): Promise | null | undefined> { 28 | throw this.unimplemented("not implemented yet"); 29 | } 30 | 31 | checkTrialIntroEligibility(params: { 32 | ids: string[] 33 | }): Promise { 34 | throw this.unimplemented("not implemented yet"); 35 | } 36 | 37 | collectAdvertisingId(): void { 38 | throw this.unimplemented("not implemented yet"); 39 | } 40 | 41 | collectAppleSearchAdsAttribution(): void { 42 | throw this.unimplemented("not implemented yet"); 43 | } 44 | 45 | detachUserFromExperiment(params: { experimentId: string }): Promise { 46 | throw this.unimplemented("not implemented yet"); 47 | } 48 | 49 | detachUserFromRemoteConfiguration(params: { remoteConfigurationId: string }): Promise { 50 | throw this.unimplemented("not implemented yet"); 51 | } 52 | 53 | getPromotionalOffer(params: { productId: string; discountId: string | undefined }): Promise { 54 | throw this.unimplemented("not implemented yet"); 55 | } 56 | 57 | identify(params: { userId: string }): Promise { 58 | throw this.unimplemented("not implemented yet"); 59 | } 60 | 61 | initialize(params: { 62 | projectKey: string; 63 | launchMode: string; 64 | environment: string; 65 | entitlementsCacheLifetime: string; 66 | proxyUrl: string | undefined; 67 | kidsMode: boolean 68 | }): void { 69 | throw this.unimplemented("not implemented yet"); 70 | } 71 | 72 | isFallbackFileAccessible(): Promise<{ success: boolean }> { 73 | throw this.unimplemented("not implemented yet"); 74 | } 75 | 76 | logout(): void { 77 | throw this.unimplemented("not implemented yet"); 78 | } 79 | 80 | offerings(): Promise { 81 | throw this.unimplemented("not implemented yet"); 82 | } 83 | 84 | presentCodeRedemptionSheet(): void { 85 | throw this.unimplemented("not implemented yet"); 86 | } 87 | 88 | products(): Promise | null | undefined> { 89 | throw this.unimplemented("not implemented yet"); 90 | } 91 | 92 | promoPurchase(params: { productId: string }): Promise | null | undefined> { 93 | throw this.unimplemented("not implemented yet"); 94 | } 95 | 96 | purchase(params: { 97 | productId: string; 98 | quantity?: number; 99 | contextKeys: string[] | null; 100 | offerId?: string | null | undefined; 101 | applyOffer?: boolean | undefined; 102 | oldProductId?: string | undefined; 103 | updatePolicyKey?: string | null | undefined 104 | }): Promise | null | undefined> { 105 | throw this.unimplemented("not implemented yet"); 106 | } 107 | 108 | remoteConfig(params: { contextKey: string | undefined }): Promise { 109 | throw this.unimplemented("not implemented yet"); 110 | } 111 | 112 | remoteConfigList(params?: { contextKeys: string[]; includeEmptyContextKey: boolean }): Promise { 113 | throw this.unimplemented("not implemented yet"); 114 | } 115 | 116 | restore(): Promise | null | undefined> { 117 | throw this.unimplemented("not implemented yet"); 118 | } 119 | 120 | setCustomUserProperty(param: { property: string; value: string }): void { 121 | throw this.unimplemented("not implemented yet"); 122 | } 123 | 124 | setDefinedUserProperty(param: { property: string; value: string }): void { 125 | throw this.unimplemented("not implemented yet"); 126 | } 127 | 128 | storeSdkInfo(params: { source: string; version: string }): void { 129 | throw this.unimplemented("not implemented yet"); 130 | } 131 | 132 | syncHistoricalData(): void { 133 | throw this.unimplemented("not implemented yet"); 134 | } 135 | 136 | syncPurchases(): void { 137 | throw this.unimplemented("not implemented yet"); 138 | } 139 | 140 | syncStoreKit2Purchases(): void { 141 | throw this.unimplemented("not implemented yet"); 142 | } 143 | 144 | userInfo(): Promise { 145 | throw this.unimplemented("not implemented yet"); 146 | } 147 | 148 | userProperties(): Promise { 149 | throw this.unimplemented("not implemented yet"); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Example Capacitor App 6 | 7 | 11 | 12 | 13 | 14 |
15 |

Qonversion Capacitor Sample Project

16 |
17 | 18 |
19 | 20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 | 28 |
29 |
30 | 31 | 32 |
33 |
34 | 35 | 36 |
37 | 38 |
39 | 40 | 41 |
42 | 43 | 44 | 45 | 46 | 47 |
48 | 49 | 50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
63 | 64 | 65 | 66 |
67 |
68 | 69 | 70 |
71 |
72 | 73 | 74 |
75 |
76 | 77 | 78 |
79 | 80 |
81 |
82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Qonversion 3 |

4 | 5 | Qonversion - In-app subscription monetization: implement subscriptions and grow your app’s revenue with A/B experiments 6 | 7 | * In-app subscription management SDK 8 | * API and webhooks to make your subscription data available where you need it 9 | * Seamless Stripe integration to enable cross-platform access management 10 | * Subscribers CRM with user-level transactions 11 | * Instant access to real-time subscription analytics 12 | * Built-in A/B experiments for subscription business model 13 | 14 |

15 | 16 | 17 |

18 | 19 | [![npm](https://img.shields.io/npm/v/qonversion-capacitor-qonversion)](https://www.npmjs.com/package/@qonversion/capacitor-plugin) 20 | [![MIT License](http://img.shields.io/cocoapods/l/Qonversion.svg?style=flat)](https://qonversion.io) 21 | 22 | 23 | ## In-App Subscription Implementation & Management 24 | 25 |

26 | 27 | 28 |

29 | 30 | 1. Qonversion SDK provides three simple methods to manage subscriptions: 31 | * Get in-app product details 32 | * Make purchases 33 | * Check subscription status to manage premium access 34 | 2. Qonversion communicates with Apple or Google platforms both through SDK and server-side to process native in-app payments and keep subscription statuses up to date. 35 | 3. You can use Qonversion webhooks and API in addition to SDK to get user-level data where you need it. 36 | 37 | See the [quick start guide documentation](https://documentation.qonversion.io/docs/quickstart). 38 | 39 | ## Analytics 40 | 41 | Qonversion provides advanced subscription analytics out of the box. You can monitor real-time metrics from new users and trial-to-paid conversions to revenue, MRR, ARR, cohort retention, and more. Understand your customers and make better decisions with precise subscription analytics. 42 | 43 |

44 | 45 | 46 |

47 | 48 | 49 | ## A/B Experiments 50 | 51 | Qonversion's A/B Experiments feature provides everything required to quickly launch paywall and other monetization experiments, analyze results and roll out winning versions without releasing a new app build. Qonversion A/B Experiments include: 52 | 53 | * User segmentation by country, install date, app version, free/paying user 54 | * Traffic allocation 55 | * Advanced subscription analytics 56 | * Visualization of A/B experiments results 57 | * Statistical significance of the results 58 | * Roll out winning versions without app release with remote config 59 | 60 | 61 |

62 | 63 | 64 |

65 | 66 | See more details [here](https://documentation.qonversion.io/docs/paywall-experiments). 67 | 68 | ## Integrations 69 | 70 | Send user-level subscription data to your favorite platforms. 71 | 72 | * Amplitude 73 | * Mixpanel 74 | * Appsflyer 75 | * Adjust 76 | * Singular 77 | * CleverTap 78 | * [All other integrations here](qonversion.io/integrations) 79 | 80 |

81 | 82 | 83 |

84 | 85 | ## Why Qonversion? 86 | 87 | * **No headaches with Apple's StoreKit & Google Billing.** Qonversion provides simple methods to handle Apple StoreKit & Google Billing purchase flow. 88 | * **Receipt validation.** Qonversion validates user receipts with Apple and Google to provide 100% accurate purchase information and subscription statuses. It also prevents unauthorized access to the premium features of your app. 89 | * **Track and increase your revenue.** Qonversion provides detailed real-time revenue analytics including cohort analysis, trial conversion rates, country segmentation, and much more. 90 | * **Integrations with the leading mobile platforms.** Qonversion allows sending data to platforms like AppsFlyer, Adjust, Branch, Tenjin, Facebook Ads, Amplitude, Mixpanel, and many others. 91 | * **Change promoted in-app products.** Change promoted in-app products anytime without app releases. 92 | * **A/B test** and identify winning in-app purchases, subscriptions or paywalls. 93 | * **Cross-device and cross-platform access management.** If you provide user authorization in your app, you can easily set Qonversion to provide premium access to authorized users across devices and operating systems. 94 | * **SDK caches the data.** Qonversion SDK caches purchase data including in-app products and entitlements, so the user experience is not affected even with a slow or interrupted network connection. 95 | * **Webhooks.** You can easily send all the data to your server with Qonversion webhooks. 96 | * **Customer support.** You can always reach out to our customer support and get the help required. 97 | 98 | Convinced? Let's go! 99 | 100 | ## Documentation 101 | 102 | Check the [full documentation](https://documentation.qonversion.io/docs/quickstart) to learn about implementation details and available features. 103 | 104 | #### Help us improve the documentation 105 | 106 | Whether you’re a core user or trying it out for the first time, you can make a valuable contribution to Qonversion by improving the documentation. Help us by: 107 | 108 | * sending us feedback about something you thought was confusing or simply missing 109 | * sending us a pull request via GitHub 110 | * suggesting better wording or ways of explaining certain topics in the [Qonversion documentation](http://documentation.qonversion.io). Use `SUGGEST EDITS` button in the top right corner. 111 | 112 | ## Contributing 113 | 114 | Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 115 | 116 | 1. Fork the Project 117 | 2. Create your Feature Branch (`git checkout -b feature/SuperFeature`) 118 | 3. Commit your Changes. Use small commits with separate logic. (`git commit -m 'Add some super feature'`) 119 | 4. Push to the Branch (`git push origin feature/SuperFeature`) 120 | 5. Open a Pull Request 121 | 122 | 123 | ## Have a question? 124 | 125 | Contact us via [issues on GitHub](https://github.com/qonversion/qonversion-capacitor/issues) or [ask a question](https://documentation.qonversion.io/discuss-new) on the site. 126 | 127 | ## License 128 | 129 | Qonversion SDK is available under the MIT license. 130 | -------------------------------------------------------------------------------- /android/src/main/java/io/qonversion/capacitor/QonversionPlugin.kt: -------------------------------------------------------------------------------- 1 | package io.qonversion.capacitor 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import com.getcapacitor.Plugin 6 | import com.getcapacitor.PluginCall 7 | import com.getcapacitor.PluginMethod 8 | import com.getcapacitor.annotation.CapacitorPlugin 9 | import io.qonversion.sandwich.ActivityProvider 10 | import io.qonversion.sandwich.QonversionEventsListener 11 | import io.qonversion.sandwich.QonversionSandwich 12 | import io.qonversion.sandwich.BridgeData 13 | 14 | private const val EntitlementsUpdatedEvent = "entitlementsUpdatedEvent" 15 | 16 | @CapacitorPlugin(name = "Qonversion") 17 | class QonversionPlugin : Plugin() { 18 | private val qonversionSandwich by lazy { 19 | (context?.applicationContext as? Application)?.let { 20 | QonversionSandwich( 21 | it, 22 | object : ActivityProvider { 23 | override val currentActivity: Activity? 24 | get() = activity 25 | }, 26 | qonversionEventsListener 27 | ) 28 | } ?: throw IllegalStateException("Failed to initialize Qonversion Sandwich. Application is null.") 29 | } 30 | 31 | private val qonversionEventsListener: QonversionEventsListener = object : 32 | QonversionEventsListener { 33 | override fun onEntitlementsUpdated(entitlements: BridgeData) { 34 | notifyListeners(EntitlementsUpdatedEvent, entitlements.toJSObject()) 35 | } 36 | } 37 | 38 | @PluginMethod 39 | fun initialize(call: PluginCall) { 40 | val context = context?.applicationContext ?: return call.reject("Can't get application context for Qonversion initialization", "InitializationError") 41 | val projectKey = call.getString("projectKey") ?: return call.noNecessaryDataError("projectKey") 42 | val launchModeKey = call.getString("launchMode") ?: return call.noNecessaryDataError("launchMode") 43 | val environmentKey = call.getString("environment") ?: return call.noNecessaryDataError("environment") 44 | val entitlementsCacheLifetimeKey = call.getString("entitlementsCacheLifetime") ?: return call.noNecessaryDataError("entitlementsCacheLifetime") 45 | val proxyUrl = call.getString("proxyUrl") 46 | val kidsMode = call.getBoolean("kidsMode") ?: false 47 | qonversionSandwich.initialize(context, projectKey, launchModeKey, environmentKey, entitlementsCacheLifetimeKey, proxyUrl, kidsMode) 48 | call.resolve() 49 | } 50 | 51 | @PluginMethod 52 | fun identify(call: PluginCall) { 53 | val userId = call.getString("userId") ?: return call.noNecessaryDataError("userId") 54 | 55 | qonversionSandwich.identify(userId, call.toResultListener()) 56 | } 57 | 58 | @PluginMethod 59 | fun logout(call: PluginCall) { 60 | qonversionSandwich.logout() 61 | call.resolve() 62 | } 63 | 64 | @PluginMethod 65 | fun purchase(call: PluginCall) { 66 | val productId = call.getString("productId") ?: return call.noNecessaryDataError("productId") 67 | val oldProductId = call.getString("oldProductId") 68 | val offerId = call.getString("offerId") 69 | val applyOffer = call.getBoolean("applyOffer") 70 | val updatePolicyKey = call.getString("updatePolicyKey") 71 | val contextKeys = call.getArray("contextKeys")?.toList() 72 | 73 | qonversionSandwich.purchase( 74 | productId, 75 | offerId, 76 | applyOffer, 77 | oldProductId, 78 | updatePolicyKey, 79 | contextKeys, 80 | call.toResultListener() 81 | ) 82 | } 83 | 84 | @PluginMethod 85 | fun updatePurchase(call: PluginCall) { 86 | purchase(call) 87 | } 88 | 89 | @PluginMethod 90 | fun checkEntitlements(call: PluginCall) { 91 | qonversionSandwich.checkEntitlements(call.toResultListener()) 92 | } 93 | 94 | @PluginMethod 95 | fun restore(call: PluginCall) { 96 | qonversionSandwich.restore(call.toResultListener()) 97 | } 98 | 99 | @PluginMethod 100 | fun offerings(call: PluginCall) { 101 | qonversionSandwich.offerings(call.toResultListener()) 102 | } 103 | 104 | @PluginMethod 105 | fun userProperties(call: PluginCall) { 106 | qonversionSandwich.userProperties(call.toResultListener()) 107 | } 108 | 109 | @PluginMethod 110 | fun isFallbackFileAccessible(call: PluginCall) { 111 | qonversionSandwich.isFallbackFileAccessible(call.toResultListener()) 112 | } 113 | 114 | @PluginMethod 115 | fun remoteConfig(call: PluginCall) { 116 | val contextKey = call.getString("contextKey") 117 | qonversionSandwich.remoteConfig(contextKey, call.toResultListener()) 118 | } 119 | 120 | @PluginMethod 121 | fun remoteConfigList(call: PluginCall) { 122 | val contextKeys = call.getArray("contextKeys")?.toList() 123 | 124 | if (contextKeys == null) { 125 | qonversionSandwich.remoteConfigList(call.toResultListener()) 126 | } else { 127 | val includeEmptyContextKey = call.getBoolean("includeEmptyContextKey") ?: return call.noNecessaryDataError("includeEmptyContextKey") 128 | 129 | qonversionSandwich.remoteConfigList(contextKeys, includeEmptyContextKey, call.toResultListener()) 130 | } 131 | } 132 | 133 | @PluginMethod 134 | fun syncHistoricalData(call: PluginCall) { 135 | qonversionSandwich.syncHistoricalData() 136 | call.resolve() 137 | } 138 | 139 | @PluginMethod 140 | fun products(call: PluginCall) { 141 | qonversionSandwich.products(call.toResultListener()) 142 | } 143 | 144 | @PluginMethod 145 | fun setDefinedUserProperty(call: PluginCall) { 146 | val rawProperty = call.getString("property") ?: return call.noNecessaryDataError("property") 147 | val value = call.getString("value") ?: return call.noNecessaryDataError("value") 148 | 149 | qonversionSandwich.setDefinedProperty(rawProperty, value) 150 | call.resolve() 151 | } 152 | 153 | @PluginMethod 154 | fun setCustomUserProperty(call: PluginCall) { 155 | val property = call.getString("property") ?: return call.noNecessaryDataError("property") 156 | val value = call.getString("value") ?: return call.noNecessaryDataError("value") 157 | 158 | qonversionSandwich.setCustomProperty(property, value) 159 | call.resolve() 160 | } 161 | 162 | @PluginMethod 163 | fun syncPurchases(call: PluginCall) { 164 | qonversionSandwich.syncPurchases() 165 | call.resolve() 166 | } 167 | 168 | @PluginMethod 169 | fun addAttributionData(call: PluginCall) { 170 | val data = call.getObject("data")?.toMap() ?: return call.noNecessaryDataError("data") 171 | 172 | if (data.isEmpty()) { 173 | return call.noNecessaryDataError("data") 174 | } 175 | 176 | val provider = call.getString("provider") ?: return call.noNecessaryDataError("provider") 177 | 178 | qonversionSandwich.addAttributionData(provider, data) 179 | call.resolve() 180 | } 181 | 182 | @PluginMethod 183 | fun checkTrialIntroEligibility(call: PluginCall) { 184 | val ids = call.getArray("ids")?.toList() ?: return call.noNecessaryDataError("ids") 185 | 186 | qonversionSandwich.checkTrialIntroEligibility(ids, call.toResultListener()) 187 | } 188 | 189 | @PluginMethod 190 | fun attachUserToExperiment(call: PluginCall) { 191 | val experimentId = call.getString("experimentId") ?: return call.noNecessaryDataError("experimentId") 192 | val groupId = call.getString("groupId") ?: return call.noNecessaryDataError("groupId") 193 | 194 | qonversionSandwich.attachUserToExperiment(experimentId, groupId, call.toResultListener()) 195 | } 196 | 197 | @PluginMethod 198 | fun detachUserFromExperiment(call: PluginCall) { 199 | val experimentId = call.getString("experimentId") ?: return call.noNecessaryDataError("experimentId") 200 | 201 | qonversionSandwich.detachUserFromExperiment(experimentId, call.toResultListener()) 202 | } 203 | 204 | @PluginMethod 205 | fun attachUserToRemoteConfiguration(call: PluginCall) { 206 | val remoteConfigurationId = call.getString("remoteConfigurationId") ?: return call.noNecessaryDataError("remoteConfigurationId") 207 | 208 | qonversionSandwich.attachUserToRemoteConfiguration(remoteConfigurationId, call.toResultListener()) 209 | } 210 | 211 | @PluginMethod 212 | fun detachUserFromRemoteConfiguration(call: PluginCall) { 213 | val remoteConfigurationId = call.getString("remoteConfigurationId") ?: return call.noNecessaryDataError("remoteConfigurationId") 214 | 215 | qonversionSandwich.detachUserFromRemoteConfiguration(remoteConfigurationId, call.toResultListener()) 216 | } 217 | 218 | @PluginMethod 219 | fun userInfo(call: PluginCall) { 220 | qonversionSandwich.userInfo(call.toResultListener()) 221 | } 222 | 223 | @PluginMethod 224 | fun storeSdkInfo(call: PluginCall) { 225 | val version = call.getString("version") ?: return call.noNecessaryDataError("version") 226 | val source = call.getString("source") ?: return call.noNecessaryDataError("source") 227 | 228 | qonversionSandwich.storeSdkInfo(source, version) 229 | call.resolve() 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /example/src/js/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | AttributionProvider, 3 | EntitlementsCacheLifetime, 4 | Environment, 5 | LaunchMode, 6 | PurchaseOptionsBuilder, 7 | Qonversion, 8 | QonversionConfigBuilder, 9 | UserPropertyKey 10 | } from "@qonversion/capacitor-plugin"; 11 | 12 | window.initializeSdk = () => { 13 | const config = new QonversionConfigBuilder( 14 | 'PV77YHL7qnGvsdmpTs7gimsxUvY-Znl2', 15 | LaunchMode.SUBSCRIPTION_MANAGEMENT, 16 | ) 17 | .setEnvironment(Environment.SANDBOX) 18 | .setEntitlementsCacheLifetime(EntitlementsCacheLifetime.MONTH) 19 | .setEntitlementsUpdateListener({ 20 | onEntitlementsUpdated(entitlements) { 21 | console.log('Entitlements updated!', entitlements); 22 | }, 23 | }) 24 | .build(); 25 | Qonversion.initialize(config); 26 | } 27 | 28 | window.purchase = async () => { 29 | const productId = document.getElementById('product-id').value; 30 | const offerId = document.getElementById('offer-id').value; 31 | const products = await Qonversion.getSharedInstance().products(); 32 | const product = products.get(productId); 33 | try { 34 | const purchaseOptions = !!offerId ? new PurchaseOptionsBuilder().setOfferId(offerId) : undefined; 35 | const entitlements = await Qonversion.getSharedInstance().purchaseProduct(product, purchaseOptions); 36 | console.log('Qonversion purchase:', entitlements, productId); 37 | } catch (e) { 38 | console.log('Qonversion purchase failed', e); 39 | } 40 | } 41 | 42 | window.getProducts = async () => { 43 | const products = await Qonversion.getSharedInstance().products(); 44 | console.log('Qonversion products:', products); 45 | } 46 | 47 | window.getPromoOffer = async () => { 48 | const productId = document.getElementById('product-id-promo').value; 49 | const discountId = document.getElementById('discount-id-promo').value; 50 | const products = await Qonversion.getSharedInstance().products(); 51 | 52 | const product = products.get(productId); 53 | if (!product) { 54 | console.log('Qonversion product not found: ', productId); 55 | return; 56 | } 57 | 58 | const discount = product.skProduct.discounts.find(discount => discount.identifier === discountId); 59 | if (!discount) { 60 | console.log('Qonversion discount not found for requested product: ', discountId); 61 | return; 62 | } 63 | 64 | try { 65 | const promoOffer = await Qonversion.getSharedInstance().getPromotionalOffer(product, discount); 66 | console.log('Qonversion getPromotionalOffer:', promoOffer); 67 | } catch (e) { 68 | console.log('Qonversion getPromotionalOffer failed', e); 69 | } 70 | } 71 | 72 | window.getRemoteConfig = async () => { 73 | const contextKey = document.getElementById('context-key').value; 74 | const key = contextKey?.length > 0 ? contextKey : undefined; 75 | const remoteConfig = await Qonversion.getSharedInstance().remoteConfig(key); 76 | console.log('Qonversion remote config:', remoteConfig, key); 77 | } 78 | 79 | window.getRemoteConfigList = async () => { 80 | const contextKeys = document.getElementById('context-keys').value; 81 | if (contextKeys?.length > 0) { 82 | const keys = contextKeys.split(', '); 83 | const remoteConfigList = await Qonversion.getSharedInstance().remoteConfigListForContextKeys(keys, true); 84 | console.log('Qonversion remote config list:', remoteConfigList, keys); 85 | } else { 86 | const remoteConfigList = await Qonversion.getSharedInstance().remoteConfigList(); 87 | console.log('Qonversion remote config list:', remoteConfigList); 88 | } 89 | } 90 | 91 | window.attachUserToExperiment = async () => { 92 | const experimentId = document.getElementById('attach-experiment-id').value; 93 | const groupId = document.getElementById('attach-group-id').value; 94 | 95 | await Qonversion.getSharedInstance().attachUserToExperiment(experimentId, groupId); 96 | console.log('Qonversion attachUserToExperiment'); 97 | } 98 | 99 | window.detachUserFromExperiment = async () => { 100 | const experimentId = document.getElementById('detach-experiment-id').value; 101 | 102 | await Qonversion.getSharedInstance().detachUserFromExperiment(experimentId); 103 | console.log('Qonversion detachUserFromExperiment'); 104 | } 105 | 106 | window.attachUserToRemoteConfiguration = async () => { 107 | const remoteConfigurationId = document.getElementById('attach-config-id').value; 108 | 109 | await Qonversion.getSharedInstance().attachUserToRemoteConfiguration(remoteConfigurationId); 110 | console.log('Qonversion attachUserToRemoteConfiguration'); 111 | } 112 | 113 | window.detachUserFromRemoteConfiguration = async () => { 114 | const remoteConfigurationId = document.getElementById('detach-config-id').value; 115 | 116 | await Qonversion.getSharedInstance().detachUserFromRemoteConfiguration(remoteConfigurationId); 117 | console.log('Qonversion detachUserFromRemoteConfiguration'); 118 | } 119 | 120 | window.isFallbackFileAccessible = async () => { 121 | const res = await Qonversion.getSharedInstance().isFallbackFileAccessible(); 122 | console.log('Qonversion isFallbackFileAccessible', res); 123 | } 124 | 125 | window.getOfferings = async () => { 126 | const offerings = await Qonversion.getSharedInstance().offerings(); 127 | console.log('Qonversion offerings:', offerings); 128 | } 129 | 130 | window.checkTrialIntroEligibility = async () => { 131 | const productIds = document.getElementById('product-ids').value; 132 | const ids = productIds.split(', '); 133 | const eligibilities = await Qonversion.getSharedInstance().checkTrialIntroEligibility(ids); 134 | console.log('Qonversion checkTrialIntroEligibility:', eligibilities, ids); 135 | } 136 | 137 | window.checkEntitlements = async () => { 138 | try { 139 | const entitlements = await Qonversion.getSharedInstance().checkEntitlements(); 140 | console.log('Qonversion checkEntitlements:', entitlements); 141 | } catch (e) { 142 | console.log('Qonversion checkEntitlements failed', e); 143 | } 144 | } 145 | 146 | window.restore = async () => { 147 | try { 148 | const entitlements = await Qonversion.getSharedInstance().restore(); 149 | console.log('Qonversion restore:', entitlements); 150 | } catch (e) { 151 | console.log('Qonversion restore failed', e); 152 | } 153 | } 154 | 155 | window.syncPurchases = async () => { 156 | await Qonversion.getSharedInstance().syncPurchases(); 157 | console.log('Qonversion syncPurchases'); 158 | } 159 | 160 | window.syncHistoricalData = async () => { 161 | await Qonversion.getSharedInstance().syncHistoricalData(); 162 | console.log('Qonversion syncHistoricalData'); 163 | } 164 | 165 | window.syncStoreKit2Purchases = async () => { 166 | await Qonversion.getSharedInstance().syncStoreKit2Purchases(); 167 | console.log('Qonversion syncHistoricalData'); 168 | } 169 | 170 | window.identify = async () => { 171 | const userId = document.getElementById('user-id').value; 172 | const user = await Qonversion.getSharedInstance().identify(userId); 173 | console.log('Qonversion identify', user); 174 | } 175 | 176 | window.logout = () => { 177 | Qonversion.getSharedInstance().logout(); 178 | console.log('Qonversion logout'); 179 | } 180 | 181 | window.userInfo = async () => { 182 | const userInfo = await Qonversion.getSharedInstance().userInfo(); 183 | console.log('Qonversion userInfo', userInfo); 184 | } 185 | 186 | window.attribution = () => { 187 | const data = { 188 | a: 'aaaa', 189 | b: { 190 | c: 25.52, 191 | }, 192 | d: null 193 | }; 194 | const provider = AttributionProvider.APPSFLYER; 195 | Qonversion.getSharedInstance().attribution(data, provider); 196 | console.log('Qonversion attribution', data, provider); 197 | } 198 | 199 | window.setUserProperty = () => { 200 | Qonversion.getSharedInstance().setUserProperty(UserPropertyKey.ADVERTISING_ID, "testAdId"); 201 | console.log('Qonversion setProperty'); 202 | } 203 | 204 | window.setCustomUserProperty = () => { 205 | Qonversion.getSharedInstance().setCustomUserProperty("test_property", "test prop value"); 206 | console.log('Qonversion setUserProperty'); 207 | } 208 | 209 | window.userProperties = async () => { 210 | const properties = await Qonversion.getSharedInstance().userProperties(); 211 | console.log('Qonversion properties', properties); 212 | } 213 | 214 | window.setEntitlementsUpdateListener = () => { 215 | Qonversion.getSharedInstance().setEntitlementsUpdateListener({ 216 | onEntitlementsUpdated(entitlements) { 217 | console.log('Entitlements updated!', entitlements); 218 | }, 219 | }); 220 | console.log('Qonversion setEntitlementsUpdateListener'); 221 | } 222 | 223 | window.collectAdvertisingId = () => { 224 | Qonversion.getSharedInstance().collectAdvertisingId(); 225 | console.log('Qonversion collectAdvertisingId'); 226 | } 227 | 228 | window.collectAppleSearchAdsAttribution = () => { 229 | Qonversion.getSharedInstance().collectAppleSearchAdsAttribution(); 230 | console.log('Qonversion collectAppleSearchAdsAttribution'); 231 | } 232 | 233 | window.setPromoPurchasesDelegate = () => { 234 | Qonversion.getSharedInstance().setPromoPurchasesDelegate({ 235 | onPromoPurchaseReceived(productId, promoPurchaseExecutor) { 236 | console.log('Promo purchase received!', productId); 237 | promoPurchaseExecutor(); 238 | }, 239 | }); 240 | console.log('Qonversion setPromoPurchasesDelegate'); 241 | } 242 | 243 | window.presentCodeRedemptionSheet = () => { 244 | Qonversion.getSharedInstance().presentCodeRedemptionSheet(); 245 | console.log('Qonversion presentCodeRedemptionSheet'); 246 | } 247 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | if ! command -v java >/dev/null 2>&1 134 | then 135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 136 | 137 | Please set the JAVA_HOME variable in your environment to match the 138 | location of your Java installation." 139 | fi 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | 201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 203 | 204 | # Collect all arguments for the java command; 205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 206 | # shell script including quotes and variable substitutions, so put them in 207 | # double quotes to make sure that they get re-expanded; and 208 | # * put everything else in single quotes, so that it's not re-expanded. 209 | 210 | set -- \ 211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 212 | -classpath "$CLASSPATH" \ 213 | org.gradle.wrapper.GradleWrapperMain \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | if ! command -v java >/dev/null 2>&1 134 | then 135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 136 | 137 | Please set the JAVA_HOME variable in your environment to match the 138 | location of your Java installation." 139 | fi 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | 201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 203 | 204 | # Collect all arguments for the java command; 205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 206 | # shell script including quotes and variable substitutions, so put them in 207 | # double quotes to make sure that they get re-expanded; and 208 | # * put everything else in single quotes, so that it's not re-expanded. 209 | 210 | set -- \ 211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 212 | -classpath "$CLASSPATH" \ 213 | org.gradle.wrapper.GradleWrapperMain \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /src/internal/QonversionInternal.ts: -------------------------------------------------------------------------------- 1 | import {registerPlugin} from '@capacitor/core'; 2 | import {AttributionProvider, QonversionErrorCode, UserPropertyKey} from "../dto/enums"; 3 | import {IntroEligibility} from "../dto/IntroEligibility"; 4 | import Mapper, {QEntitlement} from "./Mapper"; 5 | import {Offerings} from "../dto/Offerings"; 6 | import {Entitlement} from "../dto/Entitlement"; 7 | import {Product} from "../dto/Product"; 8 | import {isAndroid, isIos} from "./utils"; 9 | import {EntitlementsUpdateListener} from '../dto/EntitlementsUpdateListener'; 10 | import {PromoPurchasesListener} from '../dto/PromoPurchasesListener'; 11 | import {User} from '../dto/User'; 12 | import {PurchaseOptions} from '../dto/PurchaseOptions' 13 | import {QonversionConfig} from '../QonversionConfig'; 14 | import {RemoteConfig} from "../dto/RemoteConfig"; 15 | import {UserProperties} from '../dto/UserProperties'; 16 | import {RemoteConfigList} from '../dto/RemoteConfigList'; 17 | import {QonversionApi} from '../QonversionApi'; 18 | import {QonversionNativePlugin} from '../QonversionNativePlugin'; 19 | import {PurchaseOptionsBuilder} from '../dto/PurchaseOptionsBuilder'; 20 | import {SKProductDiscount} from '../dto/storeProducts/SKProductDiscount'; 21 | import {PromotionalOffer} from '../dto/PromotionalOffer'; 22 | 23 | const sdkVersion = "0.3.1"; 24 | 25 | const entitlementsUpdatedEvent = 'entitlementsUpdatedEvent'; 26 | const promoPurchaseEvent = 'shouldPurchasePromoProductEvent'; 27 | 28 | const QonversionNative = registerPlugin('Qonversion', { 29 | web: () => import('./web').then(m => new m.QonversionWeb()), 30 | }); 31 | 32 | export default class QonversionInternal implements QonversionApi { 33 | 34 | constructor(qonversionConfig: QonversionConfig) { 35 | QonversionNative.storeSdkInfo({source: "capacitor", version: sdkVersion}); 36 | QonversionNative.initialize({ 37 | projectKey: qonversionConfig.projectKey, 38 | launchMode: qonversionConfig.launchMode, 39 | environment: qonversionConfig.environment, 40 | entitlementsCacheLifetime: qonversionConfig.entitlementsCacheLifetime, 41 | proxyUrl: qonversionConfig.proxyUrl, 42 | kidsMode: qonversionConfig.kidsMode 43 | }); 44 | 45 | if (qonversionConfig.entitlementsUpdateListener) { 46 | this.setEntitlementsUpdateListener(qonversionConfig.entitlementsUpdateListener); 47 | } 48 | } 49 | 50 | syncHistoricalData() { 51 | QonversionNative.syncHistoricalData(); 52 | } 53 | 54 | syncStoreKit2Purchases() { 55 | if (isIos()) { 56 | QonversionNative.syncStoreKit2Purchases(); 57 | } 58 | } 59 | 60 | async getPromotionalOffer(product: Product, discount: SKProductDiscount): Promise { 61 | if (isAndroid()) { 62 | return null; 63 | } 64 | 65 | const promoOffer = await QonversionNative.getPromotionalOffer({ 66 | productId: product.qonversionID, 67 | discountId: discount.identifier, 68 | }); 69 | const mappedPromoOffer: PromotionalOffer | null = Mapper.convertPromoOffer(promoOffer); 70 | 71 | return mappedPromoOffer; 72 | } 73 | 74 | async purchaseProduct(product: Product, options: PurchaseOptions | undefined): Promise> { 75 | try { 76 | if (!options) { 77 | options = new PurchaseOptionsBuilder().build(); 78 | } 79 | 80 | let purchasePromise: Promise | null | undefined>; 81 | const promoOffer = { 82 | productDiscountId: options.promotionalOffer?.productDiscount.identifier, 83 | keyIdentifier: options.promotionalOffer?.paymentDiscount.keyIdentifier, 84 | nonce: options.promotionalOffer?.paymentDiscount.nonce, 85 | signature: options.promotionalOffer?.paymentDiscount.signature, 86 | timestamp: options.promotionalOffer?.paymentDiscount.timestamp 87 | }; 88 | 89 | if (isIos()) { 90 | purchasePromise = QonversionNative.purchase({ 91 | productId: product.qonversionID, 92 | quantity: options.quantity, 93 | contextKeys: options.contextKeys, 94 | promoOffer: promoOffer 95 | }); 96 | } else { 97 | purchasePromise = QonversionNative.purchase({ 98 | productId: product.qonversionID, 99 | offerId: options.offerId, 100 | applyOffer: options.applyOffer, 101 | oldProductId: options.oldProduct?.qonversionID, 102 | updatePolicyKey: options.updatePolicy, 103 | contextKeys: options.contextKeys 104 | }); 105 | } 106 | const entitlements = await purchasePromise; 107 | 108 | // noinspection UnnecessaryLocalVariableJS 109 | const mappedPermissions = Mapper.convertEntitlements(entitlements); 110 | 111 | return mappedPermissions; 112 | } catch (e) { 113 | e.userCanceled = e.code === QonversionErrorCode.PURCHASE_CANCELED; 114 | throw e; 115 | } 116 | } 117 | 118 | async products(): Promise> { 119 | let products = await QonversionNative.products(); 120 | const mappedProducts: Map = Mapper.convertProducts( 121 | products 122 | ); 123 | 124 | return mappedProducts; 125 | } 126 | 127 | async offerings(): Promise { 128 | let offerings = await QonversionNative.offerings(); 129 | const mappedOfferings = Mapper.convertOfferings(offerings); 130 | 131 | return mappedOfferings; 132 | } 133 | 134 | async checkTrialIntroEligibility( 135 | ids: string[] 136 | ): Promise> { 137 | const eligibilityInfo = await QonversionNative.checkTrialIntroEligibility({ids}); 138 | 139 | const mappedEligibility: Map< 140 | string, 141 | IntroEligibility 142 | > = Mapper.convertEligibility(eligibilityInfo); 143 | 144 | return mappedEligibility; 145 | } 146 | 147 | async checkEntitlements(): Promise> { 148 | const entitlements = await QonversionNative.checkEntitlements(); 149 | const mappedPermissions: Map< 150 | string, 151 | Entitlement 152 | > = Mapper.convertEntitlements(entitlements); 153 | 154 | return mappedPermissions; 155 | } 156 | 157 | async restore(): Promise> { 158 | const entitlements = await QonversionNative.restore(); 159 | 160 | const mappedPermissions: Map< 161 | string, 162 | Entitlement 163 | > = Mapper.convertEntitlements(entitlements); 164 | 165 | return mappedPermissions; 166 | } 167 | 168 | syncPurchases() { 169 | if (!isAndroid()) { 170 | return; 171 | } 172 | 173 | QonversionNative.syncPurchases(); 174 | } 175 | 176 | async identify(userID: string): Promise { 177 | const userInfo = await QonversionNative.identify({userId: userID}); 178 | const mappedUserInfo: User = Mapper.convertUserInfo(userInfo); 179 | 180 | return mappedUserInfo; 181 | } 182 | 183 | logout() { 184 | QonversionNative.logout(); 185 | } 186 | 187 | async userInfo(): Promise { 188 | const info = await QonversionNative.userInfo(); 189 | const mappedUserInfo: User = Mapper.convertUserInfo(info); 190 | 191 | return mappedUserInfo; 192 | } 193 | 194 | 195 | collectAdvertisingId() { 196 | if (isIos()) { 197 | QonversionNative.collectAdvertisingId(); 198 | } 199 | } 200 | 201 | collectAppleSearchAdsAttribution() { 202 | if (isIos()) { 203 | QonversionNative.collectAppleSearchAdsAttribution(); 204 | } 205 | } 206 | 207 | setEntitlementsUpdateListener(listener: EntitlementsUpdateListener) { 208 | QonversionNative.addListener(entitlementsUpdatedEvent, (payload: Record | null | undefined) => { 209 | const entitlements = Mapper.convertEntitlements(payload); 210 | listener.onEntitlementsUpdated(entitlements); 211 | }); 212 | } 213 | 214 | setPromoPurchasesDelegate(delegate: PromoPurchasesListener) { 215 | if (!isIos()) { 216 | return; 217 | } 218 | 219 | QonversionNative.addListener(promoPurchaseEvent, (payload: {productId: string}) => { 220 | const promoPurchaseExecutor = async () => { 221 | const entitlements = await QonversionNative.promoPurchase({productId: payload.productId}); 222 | const mappedPermissions: Map = Mapper.convertEntitlements(entitlements); 223 | return mappedPermissions; 224 | }; 225 | delegate.onPromoPurchaseReceived(payload.productId, promoPurchaseExecutor); 226 | }); 227 | } 228 | 229 | presentCodeRedemptionSheet() { 230 | if (isIos()) { 231 | QonversionNative.presentCodeRedemptionSheet(); 232 | } 233 | } 234 | 235 | async remoteConfig(contextKey: string | undefined): Promise { 236 | const remoteConfig = await QonversionNative.remoteConfig({contextKey}); 237 | const mappedRemoteConfig: RemoteConfig = Mapper.convertRemoteConfig(remoteConfig); 238 | 239 | return mappedRemoteConfig; 240 | } 241 | 242 | async remoteConfigList(): Promise { 243 | const remoteConfigList = await QonversionNative.remoteConfigList(); 244 | const mappedRemoteConfigList: RemoteConfigList = Mapper.convertRemoteConfigList(remoteConfigList); 245 | 246 | return mappedRemoteConfigList; 247 | } 248 | 249 | async remoteConfigListForContextKeys(contextKeys: string[], includeEmptyContextKey: boolean): Promise { 250 | const remoteConfigList = await QonversionNative.remoteConfigList({contextKeys, includeEmptyContextKey}); 251 | const mappedRemoteConfigList: RemoteConfigList = Mapper.convertRemoteConfigList(remoteConfigList); 252 | 253 | return mappedRemoteConfigList; 254 | } 255 | 256 | async attachUserToExperiment(experimentId: string, groupId: string): Promise { 257 | await QonversionNative.attachUserToExperiment({experimentId, groupId}); 258 | return; 259 | } 260 | 261 | async detachUserFromExperiment(experimentId: string): Promise { 262 | await QonversionNative.detachUserFromExperiment({experimentId}); 263 | return; 264 | } 265 | 266 | async attachUserToRemoteConfiguration(remoteConfigurationId: string): Promise { 267 | await QonversionNative.attachUserToRemoteConfiguration({remoteConfigurationId}); 268 | return; 269 | } 270 | 271 | async detachUserFromRemoteConfiguration(remoteConfigurationId: string): Promise { 272 | await QonversionNative.detachUserFromRemoteConfiguration({remoteConfigurationId}); 273 | return; 274 | } 275 | 276 | async isFallbackFileAccessible(): Promise { 277 | const isAccessibleResult = await QonversionNative.isFallbackFileAccessible(); 278 | 279 | return isAccessibleResult.success; 280 | } 281 | 282 | attribution(data: Object, provider: AttributionProvider) { 283 | QonversionNative.addAttributionData({data, provider}); 284 | } 285 | 286 | setUserProperty(property: UserPropertyKey, value: string) { 287 | if (property === UserPropertyKey.CUSTOM) { 288 | console.warn("Can not set user property with the key `UserPropertyKey.CUSTOM`. " + 289 | "To set custom user property, use the `setCustomUserProperty` method."); 290 | return; 291 | } 292 | 293 | QonversionNative.setDefinedUserProperty({property, value}); 294 | } 295 | 296 | setCustomUserProperty(property: string, value: string) { 297 | QonversionNative.setCustomUserProperty({property, value}); 298 | } 299 | 300 | async userProperties(): Promise { 301 | const properties = await QonversionNative.userProperties(); 302 | const mappedUserProperties: UserProperties = Mapper.convertUserProperties(properties); 303 | 304 | return mappedUserProperties; 305 | } 306 | } 307 | --------------------------------------------------------------------------------