diff --git a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS.xcodeproj/project.pbxproj b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS.xcodeproj/project.pbxproj index ef55dd8..2fcae6c 100644 --- a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS.xcodeproj/project.pbxproj +++ b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS.xcodeproj/project.pbxproj @@ -105,6 +105,13 @@ 923C373E28EDB109003CFC03 /* PostMnemonicResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 923C373D28EDB109003CFC03 /* PostMnemonicResponse.swift */; }; 923C3B9628EEF06D003CFC03 /* SubscriptionsRouteCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 923C3B9528EEF06D003CFC03 /* SubscriptionsRouteCollection.swift */; }; 923C3B9C28EEFDCF003CFC03 /* RevenueCat in Frameworks */ = {isa = PBXBuildFile; productRef = 923C3B9B28EEFDCF003CFC03 /* RevenueCat */; }; + 924E61AC28F6EB4A004DC22C /* PurchasesModelError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 924E61AB28F6EB4A004DC22C /* PurchasesModelError.swift */; }; + 924E61AE28F6EB88004DC22C /* PurchasePostBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 924E61AD28F6EB88004DC22C /* PurchasePostBody.swift */; }; + 92C2DFB828F4338900ADD0A0 /* PurchaseRouteCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92C2DFB728F4338900ADD0A0 /* PurchaseRouteCollection.swift */; }; + 92C2DFBD28F58A3B00ADD0A0 /* Offering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92C2DFBC28F58A3B00ADD0A0 /* Offering.swift */; }; + 92C2DFBF28F58BC500ADD0A0 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92C2DFBE28F58BC500ADD0A0 /* Package.swift */; }; + 92C2DFC128F594D200ADD0A0 /* PackageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92C2DFC028F594D200ADD0A0 /* PackageType.swift */; }; + 92C2DFC328F5964400ADD0A0 /* StoreProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92C2DFC228F5964400ADD0A0 /* StoreProduct.swift */; }; 92D6B3FD28E19E20004CF9DF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92D6B3FC28E19E20004CF9DF /* AppDelegate.swift */; }; 92D6B3FF28E19E20004CF9DF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92D6B3FE28E19E20004CF9DF /* SceneDelegate.swift */; }; 92D6B40128E19E20004CF9DF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92D6B40028E19E20004CF9DF /* ViewController.swift */; }; @@ -274,6 +281,13 @@ 923C373B28ED79A2003CFC03 /* Mnemonic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mnemonic.swift; sourceTree = ""; }; 923C373D28EDB109003CFC03 /* PostMnemonicResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostMnemonicResponse.swift; sourceTree = ""; }; 923C3B9528EEF06D003CFC03 /* SubscriptionsRouteCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsRouteCollection.swift; sourceTree = ""; }; + 924E61AB28F6EB4A004DC22C /* PurchasesModelError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchasesModelError.swift; sourceTree = ""; }; + 924E61AD28F6EB88004DC22C /* PurchasePostBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchasePostBody.swift; sourceTree = ""; }; + 92C2DFB728F4338900ADD0A0 /* PurchaseRouteCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseRouteCollection.swift; sourceTree = ""; }; + 92C2DFBC28F58A3B00ADD0A0 /* Offering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Offering.swift; sourceTree = ""; }; + 92C2DFBE28F58BC500ADD0A0 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; + 92C2DFC028F594D200ADD0A0 /* PackageType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageType.swift; sourceTree = ""; }; + 92C2DFC228F5964400ADD0A0 /* StoreProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreProduct.swift; sourceTree = ""; }; 92D6B3F928E19E20004CF9DF /* SOLARdVPNCommunityCoreiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SOLARdVPNCommunityCoreiOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 92D6B3FC28E19E20004CF9DF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 92D6B3FE28E19E20004CF9DF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -654,6 +668,7 @@ 923C372328E72FDA003CFC03 /* RouteCollections */ = { isa = PBXGroup; children = ( + 92C2DFB628F4337100ADD0A0 /* Purchase */, 225A83AD28EED6AE00F66619 /* DNS */, 923C372428E72FF0003CFC03 /* Nodes */, 923C3B9428EEF058003CFC03 /* Subscriptions */, @@ -719,6 +734,28 @@ path = Subscriptions; sourceTree = ""; }; + 92C2DFB628F4337100ADD0A0 /* Purchase */ = { + isa = PBXGroup; + children = ( + 92C2DFBB28F56CBE00ADD0A0 /* Models */, + 92C2DFB728F4338900ADD0A0 /* PurchaseRouteCollection.swift */, + ); + path = Purchase; + sourceTree = ""; + }; + 92C2DFBB28F56CBE00ADD0A0 /* Models */ = { + isa = PBXGroup; + children = ( + 92C2DFBC28F58A3B00ADD0A0 /* Offering.swift */, + 92C2DFBE28F58BC500ADD0A0 /* Package.swift */, + 92C2DFC028F594D200ADD0A0 /* PackageType.swift */, + 92C2DFC228F5964400ADD0A0 /* StoreProduct.swift */, + 924E61AB28F6EB4A004DC22C /* PurchasesModelError.swift */, + 924E61AD28F6EB88004DC22C /* PurchasePostBody.swift */, + ); + path = Models; + sourceTree = ""; + }; 92D6B3F028E19E20004CF9DF = { isa = PBXGroup; children = ( @@ -1096,6 +1133,7 @@ 92D6B40128E19E20004CF9DF /* ViewController.swift in Sources */, 22C3EEED28E48D9A007DB01B /* SentinelNode+Ext.swift in Sources */, 923C372628EAE93E003CFC03 /* Encoder.swift in Sources */, + 924E61AE28F6EB88004DC22C /* PurchasePostBody.swift in Sources */, 225A838028EACFD900F66619 /* Node.swift in Sources */, 92D6B41828E2DC85004CF9DF /* NodesRouteCollection.swift in Sources */, 923C371428E5B898003CFC03 /* CommonContext.swift in Sources */, @@ -1120,8 +1158,11 @@ 225A838828EAE01400F66619 /* SessionsService.swift in Sources */, 225A839A28EAE42300F66619 /* StoresConnectInfo.swift in Sources */, 225A838A28EAE01400F66619 /* SessionsServiceType.swift in Sources */, + 92C2DFBD28F58A3B00ADD0A0 /* Offering.swift in Sources */, 22488A9328E726AA00FE29C3 /* TunnelRouteCollection.swift in Sources */, + 92C2DFC128F594D200ADD0A0 /* PackageType.swift in Sources */, 22488A9128E7043200FE29C3 /* String+ArrayConversion.swift in Sources */, + 924E61AC28F6EB4A004DC22C /* PurchasesModelError.swift in Sources */, 22488AA128E72C4E00FE29C3 /* SettingsStorageStrategyType.swift in Sources */, 225A838928EAE01400F66619 /* SessionServiceError.swift in Sources */, 22C3EEF928E48F0B007DB01B /* DNSServerType.swift in Sources */, @@ -1135,6 +1176,7 @@ 22C3EEFB28E48F95007DB01B /* NotificationToken.swift in Sources */, 923C3B9628EEF06D003CFC03 /* SubscriptionsRouteCollection.swift in Sources */, 225A836B28EACE6C00F66619 /* ConnectionModelError.swift in Sources */, + 92C2DFBF28F58BC500ADD0A0 /* Package.swift in Sources */, 22C3EED428E48B52007DB01B /* TunnelsService.swift in Sources */, 225A839D28EAE54400F66619 /* SecurityService.swift in Sources */, 22C3EEE628E48D9A007DB01B /* TunnelConfiguration+Ext.swift in Sources */, @@ -1151,6 +1193,7 @@ 923C372228E72C87003CFC03 /* NodesByAddressPostBody.swift in Sources */, 225A83C028EF139A00F66619 /* PostConnectionRequest.swift in Sources */, 225A83AC28EDB3C100F66619 /* InfoEvent.swift in Sources */, + 92C2DFB828F4338900ADD0A0 /* PurchaseRouteCollection.swift in Sources */, 22C3EECE28E48B52007DB01B /* PeersModel.swift in Sources */, 2233714228F44645009D37D9 /* WalletServiceError+Ext.swift in Sources */, 92D6B41E28E2F64D004CF9DF /* ClientConstants.swift in Sources */, @@ -1171,6 +1214,7 @@ 923C372A28EAFAA5003CFC03 /* NodesServiceError.swift in Sources */, 225A83B028EED6F800F66619 /* DNSRouteCollection.swift in Sources */, 225A836C28EACE6C00F66619 /* ConnectionNodeModel.swift in Sources */, + 92C2DFC328F5964400ADD0A0 /* StoreProduct.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Common/Context/CommonContext.swift b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Common/Context/CommonContext.swift index df20523..546ce36 100644 --- a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Common/Context/CommonContext.swift +++ b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Common/Context/CommonContext.swift @@ -135,3 +135,5 @@ extension CommonContext: HasSafeStorage {} protocol HasCommonStorage { var commonStorage: SettingsStorageStrategyType { get } } extension CommonContext: HasCommonStorage {} + +extension CommonContext: NoContext {} diff --git a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Common/Utilities/ClientConstants.swift b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Common/Utilities/ClientConstants.swift index 9714524..cd7bd82 100644 --- a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Common/Utilities/ClientConstants.swift +++ b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Common/Utilities/ClientConstants.swift @@ -15,10 +15,10 @@ enum ClientConstants { static let defaultLCDHostString = "lcd-sentinel.dvpn.solar" static let defaultLCDPort = 993 - static let apiPath = "api" - static let backendURL = URL(string: "https://BACKEND")! + static let purchasesAPIKey = "" + static let apiPath = "api" static let denom = "udvpn" } diff --git a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/AppDelegate.swift b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/AppDelegate.swift index 18a4baf..36a1a31 100644 --- a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/AppDelegate.swift +++ b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/AppDelegate.swift @@ -6,14 +6,17 @@ // import UIKit +import RevenueCat @main class AppDelegate: UIResponder, UIApplicationDelegate { - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. + + if !ClientConstants.purchasesAPIKey.isEmpty { + Purchases.configure(withAPIKey: ClientConstants.purchasesAPIKey) + } + return true } @@ -30,7 +33,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } - - } - diff --git a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/Offering.swift b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/Offering.swift new file mode 100644 index 0000000..0791afe --- /dev/null +++ b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/Offering.swift @@ -0,0 +1,39 @@ +// +// Offering.swift +// SOLARdVPNCommunityCoreiOS +// +// Created by Viktoriia Kostyleva on 11.10.2022. +// + +import Foundation +import RevenueCat + +struct Offering { + let identifier: String + let serverDescription: String + let availablePackages: [Package] + let lifetime: Package? + let annual: Package? + let sixMonth: Package? + let threeMonth: Package? + let twoMonth: Package? + let monthly: Package? + let weekly: Package? +} + +extension Offering { + init(from model: RevenueCat.Offering) { + self.identifier = model.identifier + self.serverDescription = model.serverDescription + self.availablePackages = model.availablePackages.map { Package(from: $0) } + self.lifetime = Package(from: model.lifetime) + self.annual = Package(from: model.annual) + self.sixMonth = Package(from: model.sixMonth) + self.threeMonth = Package(from: model.threeMonth) + self.twoMonth = Package(from: model.twoMonth) + self.monthly = Package(from: model.monthly) + self.weekly = Package(from: model.weekly) + } +} + +extension Offering: Codable {} diff --git a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/Package.swift b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/Package.swift new file mode 100644 index 0000000..80c20d2 --- /dev/null +++ b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/Package.swift @@ -0,0 +1,39 @@ +// +// Package.swift +// SOLARdVPNCommunityCoreiOS +// +// Created by Viktoriia Kostyleva on 11.10.2022. +// + +import Foundation +import RevenueCat + +struct Package { + let identifier: String + let packageType: PackageType + let storeProduct: StoreProduct + let offeringIdentifier: String + let localizedPriceString: String + let localizedIntroductoryPriceString: String? +} + +extension Package { + init?(from optionalModel: RevenueCat.Package?) { + guard let model = optionalModel else { + return nil + } + + self.init(from: model) + } + + init(from model: RevenueCat.Package) { + self.identifier = model.identifier + self.packageType = PackageType.init(rawValue: model.packageType.rawValue) ?? .unknown + self.storeProduct = StoreProduct.init(from: model.storeProduct) + self.offeringIdentifier = model.offeringIdentifier + self.localizedPriceString = model.localizedPriceString + self.localizedIntroductoryPriceString = model.localizedIntroductoryPriceString + } +} + +extension Package: Codable {} diff --git a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/PackageType.swift b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/PackageType.swift new file mode 100644 index 0000000..71d93f8 --- /dev/null +++ b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/PackageType.swift @@ -0,0 +1,22 @@ +// +// PackageType.swift +// SOLARdVPNCommunityCoreiOS +// +// Created by Viktoriia Kostyleva on 11.10.2022. +// + +import Foundation + +enum PackageType: Int { + case unknown = -2 + case custom + case lifetime + case annual + case sixMonth + case threeMonth + case twoMonth + case monthly + case weekly +} + +extension PackageType: Codable {} diff --git a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/PurchasePostBody.swift b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/PurchasePostBody.swift new file mode 100644 index 0000000..dfabbc6 --- /dev/null +++ b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/PurchasePostBody.swift @@ -0,0 +1,12 @@ +// +// PurchasePostBody.swift +// SOLARdVPNCommunityCoreiOS +// +// Created by Viktoriia Kostyleva on 12.10.2022. +// + +import Vapor + +struct PurchasePostBody: Content { + let package_identifier: String +} diff --git a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/PurchasesModelError.swift b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/PurchasesModelError.swift new file mode 100644 index 0000000..dcd29b5 --- /dev/null +++ b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/PurchasesModelError.swift @@ -0,0 +1,19 @@ +// +// PurchasesModelError.swift +// SOLARdVPNCommunityCoreiOS +// +// Created by Viktoriia Kostyleva on 12.10.2022. +// + +import Foundation + +enum PurchasesModelError: LocalizedError { + case purchaseCancelled + + var errorDescription: String? { + switch self { + case .purchaseCancelled: + return "Purchase was canceled." + } + } +} diff --git a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/StoreProduct.swift b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/StoreProduct.swift new file mode 100644 index 0000000..a2f0e31 --- /dev/null +++ b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/Models/StoreProduct.swift @@ -0,0 +1,23 @@ +// +// StoreProduct.swift +// SOLARdVPNCommunityCoreiOS +// +// Created by Viktoriia Kostyleva on 11.10.2022. +// + +import Foundation +import RevenueCat + +struct StoreProduct { + let price: Decimal + let currency: String +} + +extension StoreProduct { + init(from model: RevenueCat.StoreProduct) { + self.price = model.price + self.currency = model.priceFormatter?.currencySymbol ?? "$" + } +} + +extension StoreProduct: Codable {} diff --git a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/PurchaseRouteCollection.swift b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/PurchaseRouteCollection.swift new file mode 100644 index 0000000..3144af3 --- /dev/null +++ b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/RouteCollections/Purchase/PurchaseRouteCollection.swift @@ -0,0 +1,128 @@ +// +// PurchaseRouteCollection.swift +// SOLARdVPNCommunityCoreiOS +// +// Created by Viktoriia Kostyleva on 10.10.2022. +// + +import Vapor +import RevenueCat + +struct PurchaseRouteCollection: RouteCollection { + typealias Context = HasWalletService + + private let context: Context + + init(context: Context) { + self.context = context + } + + func boot(routes: RoutesBuilder) throws { + routes.get("offerings", use: getOfferings) + routes.post("purchase", use: postPurchase) + } +} + +extension PurchaseRouteCollection { + func getOfferings(_ req: Request) async throws -> String { + try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation) in + getOfferings() { result in + switch result { + case let .failure(error): + continuation.resume(throwing: error.encodedError()) + case let .success(offerings): + Encoder.encode(model: offerings.map { Offering(from: $0) }, continuation: continuation) + } + } + }) + } + + func postPurchase(_ req: Request) async throws -> Response { + try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation) in + do { + let body = try req.content.decode(PurchasePostBody.self) + let identifier = body.package_identifier + + login() + + getOfferings() { result in + switch result { + case let .failure(error): + continuation.resume(throwing: error.encodedError()) + case let .success(offerings): + let package = offerings.flatMap { $0.availablePackages }.first(where: { $0.identifier == identifier }) + + guard let package = package else { + continuation.resume(throwing: Abort(.init(statusCode: 500), reason: "Failed to find package")) + + return + } + + purchase(package: package) { result in + switch result { + case let .failure(error): + continuation.resume(throwing: error.encodedError()) + case .success: + continuation.resume(returning: .init(status: .ok)) + } + } + } + } + } catch { + continuation.resume(throwing: Abort(.badRequest)) + } + }) + } +} + +// MARK: - Private + +extension PurchaseRouteCollection { + private func getOfferings(completion: @escaping (Result<[RevenueCat.Offering], Error>) -> Void) { + Purchases.shared.getOfferings { offerings, error in + guard let offerings = offerings else { + if let error = error { + log.error(error) + completion(.failure(error)) + return + } + + completion(.success([])) + return + } + + completion(.success(offerings.all.values.map { $0 })) + } + } + + private func purchase(package: RevenueCat.Package, completion: @escaping (Result) -> Void) { + Purchases.shared.purchase(package: package) { transaction, purchaserInfo, error, userCancelled in + guard !userCancelled else { + completion(.failure(PurchasesModelError.purchaseCancelled)) + return + } + + guard let error = error else { + completion(.success(())) + return + } + + log.error(error) + completion(.failure(error)) + } + } + + private func login() { + let appUserID = context.walletService.currentWalletAddress + + Purchases.shared.logIn(appUserID) { purchaserInfo, created, error in + log.info(purchaserInfo) + log.debug(created) + + if let error = error { + log.error(error) + return + } + } + } +} diff --git a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/Server/DVPNServer.swift b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/Server/DVPNServer.swift index e092d2a..b35675b 100644 --- a/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/Server/DVPNServer.swift +++ b/SolardVPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Root/Server/DVPNServer.swift @@ -31,6 +31,7 @@ extension DVPNServer { try api.register(collection: DNSRouteCollection(context: context)) try api.register(collection: SubscriptionsRouteCollection(context: context)) try api.register(collection: StorageRouteCollection(context: context)) + try api.register(collection: PurchaseRouteCollection(context: context)) try api.register( collection: TunnelRouteCollection(