feat: ios in-app purchase methods (#1652)

* Add in-app purchase methods

* fix: init StoreKit controller on startup

* fix: Add transaction details to StoreKit callbacks

* nullpointer access fixed

* feat: in app purchase for ios

* feat: add IAP product fetching and logging for iOS platform

* feat: iOS Simulator building pipeline made

* feat: add support for multiple IAP product IDs and attempt purchase of the first valid one

* feat: add support for retrieving Base64-encoded app receipt after successful IAP purchase

* refactor: inapp-purchase code cleanup

* feat: iap processing

* refactor: move to storekit 2

* feat: add request to billing

* chore: add ios ifdef

* feat: remove iOS simulator specific code and exclusions

* refactor: remove unused StoreKit 2 transaction observer and simplify IAP product fetching logic

* feat: implement StoreKit 2 for iOS and macOS, add restore purchases functionality

* fix: Restore Purchases button appearance updated

* feat: enhance error handling and duplicate config detection in ApiConfigsController

* feat: add support for Mac OS NE in-app purchases and StoreKitController

* ci-cd fix

* Revert "ci-cd fix"

This reverts commit f22fd7a13b.

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
Co-authored-by: vkamn <vk@amnezia.org>
Co-authored-by: spectrum <yyy@amnezia.org>
This commit is contained in:
Yaroslav
2025-12-18 16:36:12 +02:00
committed by GitHub
parent 46ce22b85c
commit 6178b05643
22 changed files with 910 additions and 14 deletions
+26
View File
@@ -26,6 +26,15 @@ namespace
constexpr char publicKeyInfo[] = "public_key";
constexpr char expiresAt[] = "expires_at";
}
QString normalizeVpnKey(const QString &vpnKey)
{
QString normalized = vpnKey.trimmed();
if (normalized.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
normalized = normalized.mid(QStringLiteral("vpn://").size());
}
return normalized;
}
}
ServersModel::ServersModel(std::shared_ptr<Settings> settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent)
@@ -718,6 +727,23 @@ bool ServersModel::isServerFromApiAlreadyExists(const QString &userCountryCode,
return false;
}
bool ServersModel::hasServerWithVpnKey(const QString &vpnKey) const
{
const QString normalizedInput = normalizeVpnKey(vpnKey);
if (normalizedInput.isEmpty()) {
return false;
}
for (const auto &server : std::as_const(m_servers)) {
const auto apiConfig = server.toObject().value(configKey::apiConfig).toObject();
const QString existingKey = normalizeVpnKey(apiConfig.value(apiDefs::key::vpnKey).toString());
if (!existingKey.isEmpty() && existingKey == normalizedInput) {
return true;
}
}
return false;
}
bool ServersModel::serverHasInstalledContainers(const int serverIndex) const
{
QJsonObject server = m_servers.at(serverIndex).toObject();