Skip to content

Commit 8dca6ea

Browse files
committed
fix: upgrade existing android subscription and add renew logic
1 parent 1377330 commit 8dca6ea

3 files changed

Lines changed: 35 additions & 6 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
44
set(CMAKE_CXX_STANDARD_REQUIRED ON)
55

66
set(PROJECT AmneziaVPN)
7-
set(AMNEZIAVPN_VERSION 5.0.0.6)
7+
set(AMNEZIAVPN_VERSION 5.0.1.3)
88

99
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
1010
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
@@ -28,7 +28,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
2828
set(RELEASE_DATE "${CURRENT_DATE}")
2929

3030
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
31-
set(APP_ANDROID_VERSION_CODE 2141)
31+
set(APP_ANDROID_VERSION_CODE 2143)
3232

3333
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
3434
set(MZ_PLATFORM_NAME "linux")

client/android/billing/src/main/kotlin/BillingProvider.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,13 @@ class BillingProvider(context: Context) : AutoCloseable {
262262
.build()
263263

264264
val subscriptionUpdateParams = oldPurchaseToken?.let {
265+
// Per Google Play docs, switching between auto-renewing plans within the same
266+
// subscription only supports CHARGE_FULL_PRICE or WITHOUT_PRORATION - any other mode
267+
// (e.g. CHARGE_PRORATED_PRICE) is rejected with DEVELOPER_ERROR "Requested replacement
268+
// mode is not supported for this request".
265269
BillingFlowParams.SubscriptionUpdateParams.newBuilder()
266270
.setOldPurchaseToken(oldPurchaseToken)
267-
.setSubscriptionReplacementMode(ReplacementMode.WITHOUT_PRORATION)
271+
.setSubscriptionReplacementMode(ReplacementMode.CHARGE_FULL_PRICE)
268272
.build()
269273
}
270274

client/core/controllers/api/subscriptionController.cpp

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,22 @@ ErrorCode SubscriptionController::processPlayMarketPurchase(const QString &userC
981981
QObject::connect(&watcher, &QFutureWatcher<QPair<bool, QString>>::finished, &waitLoop, &QEventLoop::quit);
982982

983983
QFuture<QPair<bool, QString>> future = QtConcurrent::run([androidController, productId]() {
984+
// If the user already has an active "premium" subscription, upgrade/replace it with proration
985+
// instead of stacking a second, independent purchase that Google Play would just queue behind it.
986+
QString oldPurchaseToken;
987+
QJsonObject existingPurchasesResult = androidController->queryPurchases();
988+
if (existingPurchasesResult.value("responseCode").toInt(-1) == 0) {
989+
const QJsonArray existingPurchases = existingPurchasesResult.value("purchases").toArray();
990+
for (const QJsonValue &purchaseValue : existingPurchases) {
991+
const QJsonObject existingPurchase = purchaseValue.toObject();
992+
if (existingPurchase.value("purchaseState").toInt(-1) == 1) { // PURCHASED
993+
oldPurchaseToken = existingPurchase.value("purchaseToken").toString();
994+
qInfo() << "[Billing] Found existing active subscription, will upgrade instead of purchasing a new one";
995+
break;
996+
}
997+
}
998+
}
999+
9841000
QJsonObject plansResult = androidController->getSubscriptionPlans();
9851001
int responseCode = plansResult.value("responseCode").toInt(-1);
9861002
if (responseCode != 0) {
@@ -998,11 +1014,18 @@ ErrorCode SubscriptionController::processPlayMarketPurchase(const QString &userC
9981014
if (offer.value("basePlanId").toString() != productId) continue;
9991015

10001016
const QString token = offer.value("offerToken").toString();
1001-
if (fallbackOfferToken.isEmpty()) fallbackOfferToken = token;
1002-
10031017
QJsonArray pricingPhases = offer.value("pricingPhases").toArray();
10041018
const bool hasFreeTrial = !pricingPhases.isEmpty()
10051019
&& pricingPhases.first().toObject().value("priceAmountMicros").toDouble() == 0;
1020+
1021+
// Google Play's subscription replacement API rejects switching to an offer with an
1022+
// introductory/trial phase ("Requested replacement mode is not supported for this
1023+
// request"), so an upgrade must always target the regular, non-trial offer - skip
1024+
// trial offers entirely here rather than just deprioritizing them.
1025+
if (hasFreeTrial && !oldPurchaseToken.isEmpty()) continue;
1026+
1027+
if (fallbackOfferToken.isEmpty()) fallbackOfferToken = token;
1028+
10061029
if (hasFreeTrial) {
10071030
offerToken = token;
10081031
qInfo() << "[Billing] Found free trial offer for basePlanId:" << productId;
@@ -1016,7 +1039,9 @@ ErrorCode SubscriptionController::processPlayMarketPurchase(const QString &userC
10161039
qWarning() << "[Billing] No offer token found for basePlanId:" << productId;
10171040
return qMakePair(false, QString());
10181041
}
1019-
QJsonObject purchaseResult = androidController->purchaseSubscription(offerToken);
1042+
QJsonObject purchaseResult = oldPurchaseToken.isEmpty()
1043+
? androidController->purchaseSubscription(offerToken)
1044+
: androidController->upgradeSubscription(offerToken, oldPurchaseToken);
10201045
responseCode = purchaseResult.value("responseCode").toInt(-1);
10211046
if (responseCode != 0) {
10221047
qWarning() << "[Billing] Purchase failed, responseCode:" << responseCode;

0 commit comments

Comments
 (0)