Skip to content

Commit

Permalink
Merge pull request #286 from qonversion/release/7.3.0
Browse files Browse the repository at this point in the history
Release 7.3.0
  • Loading branch information
suriksarkisyan committed Jan 12, 2024
2 parents 9d4c69a + cdd38a9 commit 163cbf1
Show file tree
Hide file tree
Showing 21 changed files with 400 additions and 54 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 7.3.0
* Updated entitlements fields

## 7.2.0
* Added attach/detach remote configuration functions

Expand Down
75 changes: 44 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
Qonversion
</h1>

Qonversion is the data platform to power in-app subscription revenue growth.
Qonversion - In-app subscription monetization: implement subscriptions and grow your app’s revenue with A/B experiments

* fast in-app subscriptions implementation
* back-end infrastructure to validate user receipts
* manage cross-platform user access to paid content on your app
* comprehensive subscription analytics
* out-of-the-box integrations with the leading marketing, attribution, and product analytics platforms
* push notifications and in-app messaging to win back lapsed subscribers
* A/B Testing for in-app purchases
* In-app subscription management SDK
* API and webhooks to make your subscription data available where you need it
* Seamless Stripe integration to enable cross-platform access management
* Subscribers CRM with user-level transactions
* Instant access to real-time subscription analytics
* Built-in A/B experiments for subscription business model

<p align="center">
<a href="https://qonversion.io"><img width="90%" src="https://qcdn3.sfo3.digitaloceanspaces.com/github/qonversion_platform.png">
Expand All @@ -21,50 +20,65 @@ Qonversion is the data platform to power in-app subscription revenue growth.
[![MIT License](https://img.shields.io/cocoapods/l/Qonversion.svg?style=flat)](https://qonversion.io)


## How It Works: Product Center
## In-App Subscription Implementation & Management

<p align="center">
<a href="https://documentation.qonversion.io/docs/integrations-overview"><img width="90%" src="https://user-images.githubusercontent.com/13959241/161107203-8ef3ecee-86be-47a2-ac57-b21d3da19339.png">
</a>
</p>

1. Application calls the purchase method of Qonversion SDK.
2. Qonversion SDK communicates with StoreKit or Google Billing Client to make a purchase.
3. If a purchase is successful, the SDK sends a request to Qonversion API for server-to-server purchase validation. Qonversion server receives accurate information on the in-app purchase status and user entitlements.
4. SDK returns control to the application with a processing state.
1. Qonversion SDK provides three simple methods to manage subscriptions:
* Get in-app product details
* Make purchases
* Check subscription status to manage premium access
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.
3. You can use Qonversion webhooks and API in addition to SDK to get user-level data where you need it.

See the [quick start guide documentation](https://documentation.qonversion.io/docs/quickstart).

## Analytics

Monitor your in-app revenue metrics. Understand your customers and make better decisions with precise subscription revenue data.
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.

<p align="center">
<a href="https://documentation.qonversion.io/docs/analytics"><img width="90%" src="https://qonversion.io/img/screenshots/desktop/mobile_subscription_analytics.jpg">
<a href="https://documentation.qonversion.io/docs/analytics"><img width="90%" src="https://files.readme.io/9a4fdf6-Analytics.png">
</a>
</p>

## Integrations

Send subscription data to your favorite platforms. Share your mobile and web subscription data using our powerful integrations.
## A/B Experiments

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:

* User segmentation by country, install date, app version, free/paying user
* Traffic allocation
* Advanced subscription analytics
* Visualization of A/B experiments results
* Statistical significance of the results
* Roll out winning versions without app release with remote config


<p align="center">
<a href="https://documentation.qonversion.io/docs/integrations-overview"><img width="90%" src="https://qcdn3.sfo3.digitaloceanspaces.com/github/integrations.png">
<a href="https://documentation.qonversion.io/docs/subscription-ab-testing"><img width="90%" src="https://qcdn3.sfo3.digitaloceanspaces.com/github/ab_tests.png">
</a>
</p>

## Personalized push notifications & in-app messaging

Qonversion allows sending automated, personalized push notifications and in-app messages initiated by in-app purchase events. This feature is designed to increase your app's revenue and retention, provide cancellation insights, reduce subscriber churn, and improve your subscribers' user experience.
See more details [here](https://documentation.qonversion.io/docs/paywall-experiments).

## Integrations

See more in the [documentation](https://documentation.qonversion.io/docs/automations)
![](https://qonversion.io/img/@2x/automation/in-app-constructor.gif)

## A/B Testing for in-app purchases
Send user-level subscription data to your favorite platforms.

Boost conversion rates with paywalls and in-app purchases A/B testing. Find the best pricing and paywall variations. Be flexible to prove hypotheses without app releases.
* Amplitude
* Mixpanel
* Appsflyer
* Adjust
* Singular
* CleverTap
* [All other integrations here](qonversion.io/integrations)

<p align="center">
<a href="https://documentation.qonversion.io/docs/subscription-ab-testing"><img width="90%" src="https://user-images.githubusercontent.com/13959241/161716071-b30311b3-b60f-482d-a5d3-c40c1951253b.png">
<a href="https://documentation.qonversion.io/docs/integrations-overview"><img width="90%", src="https://qcdn3.sfo3.digitaloceanspaces.com/github/integrations.png">
</a>
</p>

Expand All @@ -75,18 +89,17 @@ Boost conversion rates with paywalls and in-app purchases A/B testing. Find the
* **Track and increase your revenue.** Qonversion provides detailed real-time revenue analytics including cohort analysis, trial conversion rates, country segmentation, and much more.
* **Integrations with the leading mobile platforms.** Qonversion allows sending data to platforms like AppsFlyer, Adjust, Branch, Tenjin, Facebook Ads, Amplitude, Mixpanel, and many others.
* **Change promoted in-app products.** Change promoted in-app products anytime without app releases.
* **Win back lapsed subscribers.** Qonversion allows sending highly targeted push notifications triggered by server-side subscription events. You can send special offers to users who just canceled a free trial or a subscription. Plus you can deliver in-app messages with a beautiful native design that you create in Qonversion.
* **A/B test** and identify winning in-app purchases, subscriptions or paywals.
* **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.
* **SDK caches the data.** Qonversion SDK caches purchase data including in-app products and permissions, so the user experience is not affected even with the slow or interrupting network connection.
* **SDK caches the data.** Qonversion SDK caches purchase data including in-app products and entitlements, so the user experience is not affected even with the slow or interrupting network connection.
* **Webhooks.** You can easily send all of the data to your server with Qonversion webhooks.
* **Customer support.** You can always reach out to our customer support and get the help required.

Convinced? Let's go!

## Documentation

Check the [documentation](https://documentation.qonversion.io/docs/quickstart) to learn details on implementing and using Qonversion SDKs.
Check the [full documentation](https://documentation.qonversion.io/docs/quickstart) to learn about implementation details and available features.

#### Help us improve the documentation

Expand All @@ -113,4 +126,4 @@ Contact us via [issues on GitHub](https://github.com/qonversion/flutter-sdk/issu

## License

Qonversion SDK is available under the MIT license.
Qonversion SDK is available under the MIT license.
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "io.qonversion.sandwich:sandwich:3.2.0"
implementation "io.qonversion.sandwich:sandwich:3.3.3"
implementation 'com.google.code.gson:gson:2.9.0'
}
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ class QonversionPlugin : MethodCallHandler, FlutterPlugin, ActivityAware {
}

private fun checkEntitlements(result: Result) {
qonversionSandwich.checkEntitlements(result.toResultListener())
qonversionSandwich.checkEntitlements(result.toJsonResultListener())
}

private fun restore(result: Result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ internal fun MethodChannel.Result.toPurchaseResultListener(): PurchaseResultList
}

override fun onSuccess(data: Map<String, Any?>) {
success(data)
success(Gson().toJson(data))
}
}
}
14 changes: 7 additions & 7 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = MTVL2X9L7N;
DevelopmentTeam = 5ZBNSPDUJ2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
};
Expand Down Expand Up @@ -436,7 +436,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = MTVL2X9L7N;
DEVELOPMENT_TEAM = 5ZBNSPDUJ2;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
Expand All @@ -449,7 +449,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.qonversion.sample;
PRODUCT_BUNDLE_IDENTIFIER = io.qonversion.sample;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
Expand Down Expand Up @@ -574,7 +574,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = MTVL2X9L7N;
DEVELOPMENT_TEAM = 5ZBNSPDUJ2;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
Expand All @@ -587,7 +587,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.qonversion.sample;
PRODUCT_BUNDLE_IDENTIFIER = io.qonversion.sample;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
Expand All @@ -607,7 +607,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = MTVL2X9L7N;
DEVELOPMENT_TEAM = 5ZBNSPDUJ2;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
Expand All @@ -620,7 +620,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.qonversion.sample;
PRODUCT_BUNDLE_IDENTIFIER = io.qonversion.sample;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
Expand Down
12 changes: 10 additions & 2 deletions ios/Classes/SwiftQonversionPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public class SwiftQonversionPlugin: NSObject, FlutterPlugin {
}

private func checkEntitlements(_ result: @escaping FlutterResult) {
qonversionSandwich?.checkEntitlements(getDefaultCompletion(result))
qonversionSandwich?.checkEntitlements(getJsonCompletion(result))
}

private func remoteConfig(_ result: @escaping FlutterResult) {
Expand Down Expand Up @@ -388,7 +388,15 @@ public class SwiftQonversionPlugin: NSObject, FlutterPlugin {
return result(FlutterError.purchaseError(error))
}

result(data)
guard let data = data else {
return result(nil)
}

guard let jsonData = data.toJson() else {
return result(FlutterError.serializationError)
}

result(jsonData)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion ios/qonversion_flutter.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Pod::Spec.new do |s|
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.platform = :ios, '9.0'
s.dependency "QonversionSandwich", "3.2.0"
s.dependency "QonversionSandwich", "3.3.3"

# Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }
Expand Down
72 changes: 70 additions & 2 deletions lib/src/dto/entitlement.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:qonversion_flutter/src/dto/entitlement_source.dart';
import 'package:qonversion_flutter/src/dto/entitlement_renew_state.dart';
import 'package:qonversion_flutter/src/dto/transaction.dart';
import 'package:qonversion_flutter/src/internal/mapper.dart';

import 'entitlement_grant_type.dart';

part 'entitlement.g.dart';

@JsonSerializable(createToJson: false)
Expand Down Expand Up @@ -47,6 +50,64 @@ class QEntitlement {
)
final DateTime? expirationDate;

/// Renews count for the entitlement. Renews count starts from the second paid subscription.
/// For example, we have 20 transactions. One is the trial, and one is the first paid transaction after the trial.
/// Renews count is equal to 18.
@JsonKey(
name: 'renewsCount',
defaultValue: 0
)
final int renewsCount;

/// Trial start date.
@JsonKey(
name: 'trialStartTimestamp',
fromJson: QMapper.dateTimeFromNullableSecondsTimestamp,
)
final DateTime? trialStartDate;

/// First purchase date.
@JsonKey(
name: 'firstPurchaseTimestamp',
fromJson: QMapper.dateTimeFromNullableSecondsTimestamp,
)
final DateTime? firstPurchaseDate;

/// Last purchase date.
@JsonKey(
name: 'lastPurchaseTimestamp',
fromJson: QMapper.dateTimeFromNullableSecondsTimestamp,
)
final DateTime? lastPurchaseDate;

/// Last activated offer code.
@JsonKey(
name: 'lastActivatedOfferCode'
)
final String? lastActivatedOfferCode;

/// Grant type of the entitlement.
@JsonKey(
name: 'grantType',
unknownEnumValue: QEntitlementGrantType.purchase,
fromJson: QMapper.grantTypeFromNullableValue
)
final QEntitlementGrantType grantType;

/// Auto-renew disable date.
@JsonKey(
name: 'autoRenewDisableTimestamp',
fromJson: QMapper.dateTimeFromNullableSecondsTimestamp,
)
final DateTime? autoRenewDisableDate;

/// Array of the transactions that unlocked current entitlement.
@JsonKey(
name: 'transactions',
fromJson: QMapper.transactionsFromNullableValue
)
final List<QTransaction> transactions;

/// Use for checking entitlement for current user.
/// Pay attention, isActive == true does not mean that subscription is renewable.
/// Subscription could be canceled, but the user could still have a entitlement
Expand All @@ -61,8 +122,15 @@ class QEntitlement {
this.startedDate,
this.expirationDate,
this.isActive,
this.renewsCount,
this.trialStartDate,
this.firstPurchaseDate,
this.lastPurchaseDate,
this.lastActivatedOfferCode,
this.grantType,
this.autoRenewDisableDate,
this.transactions
);

factory QEntitlement.fromJson(Map<String, dynamic> json) =>
_$QEntitlementFromJson(json);
factory QEntitlement.fromJson(Map<String, dynamic> json) => _$QEntitlementFromJson(json);
}
12 changes: 12 additions & 0 deletions lib/src/dto/entitlement.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions lib/src/dto/entitlement_grant_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:json_annotation/json_annotation.dart';

enum QEntitlementGrantType {
@JsonValue('Purchase')
purchase,

@JsonValue('FamilySharing')
familySharing,

@JsonValue('OfferCode')
offerCode,

@JsonValue('Manual')
manual,
}
Loading

0 comments on commit 163cbf1

Please sign in to comment.