mirror of
https://github.com/ProtonMail/protoncore_ios.git
synced 2026-01-16 23:00:24 +00:00
Feat: [CP-8865] Add userTransaction UUID to PaymentsV2
Refs: https://gitlab.protontech.ch/apple/shared/protoncore/-/merge_requests/1969
This commit is contained in:
commit
776a8638ac
12 changed files with 211 additions and 74 deletions
|
|
@ -199,7 +199,7 @@
|
|||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Something went wrong, please retry. If the issue persists please contact our support team"
|
||||
"value" : "Something went wrong, please retry. If the issue persists please contact our support team."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public extension JSONDecoder.KeyDecodingStrategy {
|
|||
static var lowerCamelCase: JSONDecoder.KeyDecodingStrategy {
|
||||
.custom {
|
||||
// this has been added convert the ID JSON key to the most common used id
|
||||
if $0.last!.stringValue == "ID" {
|
||||
if $0.last!.stringValue == "ID" || $0.last!.stringValue == "UUID" {
|
||||
return AnyKey(stringValue: $0.last!.stringValue.lowercased())!
|
||||
}
|
||||
let currentKey = $0.last!.stringValue
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ public protocol ProtonTransactionProviding {
|
|||
var originalID: UInt64 { get }
|
||||
var productID: String { get }
|
||||
var price: Decimal? { get }
|
||||
var userTransactionUUID: UUID? { get }
|
||||
var currencyIdentifier: String? { get }
|
||||
}
|
||||
|
||||
|
|
@ -45,6 +46,7 @@ public struct ProtonTransaction: ProtonTransactionProviding {
|
|||
public var originalID: UInt64
|
||||
public var productID: String
|
||||
public var price: Decimal?
|
||||
public var userTransactionUUID: UUID?
|
||||
public var currencyIdentifier: String?
|
||||
}
|
||||
|
||||
|
|
@ -58,6 +60,7 @@ extension Transaction {
|
|||
originalID: originalID,
|
||||
productID: productID,
|
||||
price: price,
|
||||
userTransactionUUID: appAccountToken,
|
||||
currencyIdentifier: currencyCode )
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// UserTransactionUUIDResponse.swift
|
||||
// PaymentsV2 - Created on 15/10/2024.
|
||||
//
|
||||
// Copyright (c) 2024 Proton Technologies AG
|
||||
//
|
||||
// This file is part of Proton Technologies AG.
|
||||
//
|
||||
// ProtonCore is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonCore is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct UserTransactionUUIDResponse: Decodable {
|
||||
let code: Int
|
||||
let uuid: String
|
||||
|
||||
var uuidValue: UUID? {
|
||||
UUID(uuidString: uuid)
|
||||
}
|
||||
}
|
||||
|
|
@ -31,10 +31,31 @@ public struct PaymentsAPIs {
|
|||
|
||||
private struct Constants {
|
||||
static var envPrefix = "https://"
|
||||
static var moduleNameSpace = "/payments/"
|
||||
static func moduleNameSpace(requestType: RequestType) -> String {
|
||||
switch requestType {
|
||||
case .userTransactionUUID:
|
||||
return "/auth/"
|
||||
default:
|
||||
return "/payments/"
|
||||
}
|
||||
}
|
||||
|
||||
static func apiVersion(requestType: RequestType) -> APIv {
|
||||
switch requestType {
|
||||
case .userTransactionUUID:
|
||||
return .v4
|
||||
default:
|
||||
return .v5
|
||||
}
|
||||
}
|
||||
|
||||
static func urlString(requestType: RequestType, baseURL: String) -> String {
|
||||
return Constants.envPrefix + baseURL + Constants.moduleNameSpace(requestType: requestType) + Constants.apiVersion(requestType: requestType).rawValue + requestType.requestEndpoint
|
||||
}
|
||||
}
|
||||
|
||||
private enum APIv: String {
|
||||
case v4
|
||||
case v5
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +64,7 @@ public struct PaymentsAPIs {
|
|||
|
||||
public func url(for api: RequestType) throws -> APIRequest {
|
||||
|
||||
let urlString = Constants.envPrefix + envURL.baseUrl + Constants.moduleNameSpace + version.rawValue + api.requestEndpoint
|
||||
let urlString = Constants.urlString(requestType: api, baseURL: envURL.baseUrl)
|
||||
var urlComponents = URLComponents(string: urlString)
|
||||
|
||||
if let queryItems = api.queryComponents {
|
||||
|
|
@ -88,6 +109,7 @@ public enum RequestType {
|
|||
|
||||
// MARK: Miscellaneous
|
||||
case icon(name: String)
|
||||
case userTransactionUUID
|
||||
}
|
||||
|
||||
extension RequestType {
|
||||
|
|
@ -112,6 +134,8 @@ extension RequestType {
|
|||
return "/plans"
|
||||
case .icon(let iconName):
|
||||
return "/resources/icons/\(iconName)"
|
||||
case .userTransactionUUID:
|
||||
return "/sessions/uuid"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,6 +163,8 @@ extension RequestType {
|
|||
return nil
|
||||
case .icon:
|
||||
return nil
|
||||
case .userTransactionUUID:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,6 +197,8 @@ extension RequestType {
|
|||
return generateQueryParameters(parameters: queryParams)
|
||||
case .icon:
|
||||
return nil
|
||||
case .userTransactionUUID:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public class RemoteManager: RemoteManagerProviding {
|
|||
|
||||
public func updateSession(sessionID: String, authToken: String) {
|
||||
requestHTTPHeader[.sessionId] = sessionID
|
||||
requestHTTPHeader[.authorization] = "Bearer \(authToken)"
|
||||
requestHTTPHeader[.authorization] = "Bearer \(authToken)"
|
||||
}
|
||||
|
||||
// MARK: Private methods
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ public enum ProtonPlansManagerError: Error {
|
|||
case unableToCreateRequest
|
||||
case unableToFetchProductsFromStore
|
||||
case unableToMatchProtonPlanToStoreProduct
|
||||
case unableToGetUserTransactionUUID
|
||||
|
||||
// Transaction error
|
||||
case transactionNotFound
|
||||
|
|
@ -138,7 +139,8 @@ public final class ProtonPlansManager: NSObject, ProtonPlansManagerProviding {
|
|||
self.planName = planName
|
||||
self.planCycle = planCycle
|
||||
|
||||
let result = try await product.purchase()
|
||||
let userTransactionUUID = try await generateUserTransactionUUID()
|
||||
let result = try await product.purchase(options: [.appAccountToken(userTransactionUUID)])
|
||||
|
||||
switch result {
|
||||
case .success(let verificationResult):
|
||||
|
|
@ -170,6 +172,23 @@ public final class ProtonPlansManager: NSObject, ProtonPlansManagerProviding {
|
|||
}
|
||||
}
|
||||
|
||||
private func generateUserTransactionUUID() async throws -> UUID {
|
||||
guard let request = try? paymentsAPI.url(for: .userTransactionUUID) else {
|
||||
throw ProtonPlansManagerError.unableToGetUserTransactionUUID
|
||||
}
|
||||
|
||||
do {
|
||||
let uuidStrign: UserTransactionUUIDResponse = try await remoteManager.getFromURL(request.url)
|
||||
guard let uuid = UUID(uuidString: uuidStrign.uuid) else {
|
||||
throw ProtonPlansManagerError.unableToGetUserTransactionUUID
|
||||
}
|
||||
|
||||
return uuid
|
||||
} catch {
|
||||
throw ProtonPlansManagerError.unableToGetUserTransactionUUID
|
||||
}
|
||||
}
|
||||
|
||||
private func findMatchingPlan(productID: String) -> ComposedPlan? {
|
||||
planComposer.matchPlanToStoreProduct(productID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,18 +22,22 @@
|
|||
import Foundation
|
||||
import StoreKit
|
||||
|
||||
enum StoreKitReceiptManagerError: Error {
|
||||
case unableToExtractReceiptData
|
||||
}
|
||||
|
||||
public protocol StoreKitReceiptManagerProviding {
|
||||
func fetchPurchaseReceipt() throws -> String
|
||||
}
|
||||
|
||||
final public class StoreKitReceiptManager: StoreKitReceiptManagerProviding {
|
||||
public final class StoreKitReceiptManager: StoreKitReceiptManagerProviding {
|
||||
|
||||
public init() {}
|
||||
|
||||
public func fetchPurchaseReceipt() throws -> String {
|
||||
guard let url = Bundle.main.appStoreReceiptURL, let data = try? Data(contentsOf: url) else {
|
||||
debugPrint("Unable to get receipt data")
|
||||
throw ProtonPlansManagerError.unableToExtractReceiptData
|
||||
throw StoreKitReceiptManagerError.unableToExtractReceiptData
|
||||
}
|
||||
|
||||
return data.base64EncodedString()
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ public enum TransactionType {
|
|||
case failed
|
||||
case renewal
|
||||
case alreadyProcessed
|
||||
case transactionUUIDNotFoundOrMismatching
|
||||
case unableToVerifyAccountsUUIDs
|
||||
case unknown
|
||||
}
|
||||
|
||||
|
|
@ -81,13 +83,22 @@ public final class StoreObserver {
|
|||
Task(priority: .background) {
|
||||
for await update in Transaction.updates {
|
||||
if let transaction = try? update.payloadValue {
|
||||
guard let plan = planComposer?.matchPlanToStoreProduct(transaction.productID) else {
|
||||
guard let plan = planComposer?.matchPlanToStoreProduct(transaction.productID), let appAccountToken = transaction.appAccountToken else {
|
||||
return
|
||||
}
|
||||
do {
|
||||
_ = try await transactionHandler?.processTransaction(transaction.toProtonTransaction(), plan: plan)
|
||||
await transaction.finish()
|
||||
transactionStatus = .successful
|
||||
guard let accountMatching = try await transactionHandler?.verifyTransactionUUIDs(appAccountToken: appAccountToken) else {
|
||||
transactionStatus = .unableToVerifyAccountsUUIDs
|
||||
return
|
||||
}
|
||||
if accountMatching {
|
||||
_ = try await transactionHandler?.processTransaction(transaction.toProtonTransaction(), plan: plan)
|
||||
await transaction.finish()
|
||||
transactionStatus = .successful
|
||||
} else {
|
||||
transactionStatus = .transactionUUIDNotFoundOrMismatching
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
debugPrint(error)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,14 +19,20 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import Foundation
|
||||
import ProtonCoreObservability
|
||||
import ProtonCoreNetworking
|
||||
import StoreKit
|
||||
|
||||
public enum TransactionHandlerError: Error {
|
||||
|
||||
case unableToCreateRequest
|
||||
case unableToFindPlanName
|
||||
case unableToFindMatchingPlan
|
||||
case transactionIdNotEqualToOriginalTransactionId
|
||||
case userTransactionUUIDNotMatching
|
||||
case unableToGetBundleIdentifier
|
||||
case unableToGetTransactionAmountOrCurrency
|
||||
}
|
||||
|
||||
public enum TransactionHandlerState: String {
|
||||
|
|
@ -74,6 +80,7 @@ public final class TransactionHandler {
|
|||
}
|
||||
|
||||
try await resolveTransaction(transaction, plan: plan)
|
||||
|
||||
// transaction.appAccountToken
|
||||
// add API to fetch account UUID from BE --> AccountUUID
|
||||
// if transaction.appAccountToken == AccountUUID --> Process
|
||||
|
|
@ -83,6 +90,17 @@ public final class TransactionHandler {
|
|||
public func updateRemoteManager(remoteManager: RemoteManagerProviding) {
|
||||
self.remoteManager = remoteManager
|
||||
}
|
||||
|
||||
public func verifyTransactionUUIDs(appAccountToken: UUID) async throws -> Bool {
|
||||
|
||||
guard let request = try? paymentsAPIs.url(for: .userTransactionUUID) else {
|
||||
throw TransactionHandlerError.unableToCreateRequest
|
||||
}
|
||||
debugPrint("Fetching user transaction UUID")
|
||||
|
||||
let userUUID: UserTransactionUUIDResponse = try await remoteManager.getFromURL(request.url)
|
||||
return appAccountToken == userUUID.uuidValue
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private methods
|
||||
|
|
@ -97,13 +115,13 @@ private extension TransactionHandler {
|
|||
let transactionIdentifier = transaction.originalID
|
||||
guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
|
||||
debugPrint("bundle not obtainable")
|
||||
throw ProtonPlansManagerError.unableToGetBundleIdentifier
|
||||
throw TransactionHandlerError.unableToGetBundleIdentifier
|
||||
}
|
||||
|
||||
guard let amount = transaction.price, let currency = transaction.currencyIdentifier else {
|
||||
debugPrint("Impossible to get amount and currency from transaction")
|
||||
transactionState = .transactionProcessError
|
||||
throw ProtonPlansManagerError.unableToGetTransactionAmountOrCurrency
|
||||
throw TransactionHandlerError.unableToGetTransactionAmountOrCurrency
|
||||
}
|
||||
|
||||
transactionState = .generatingReceipt
|
||||
|
|
@ -125,7 +143,7 @@ private extension TransactionHandler {
|
|||
|
||||
guard let request = try? paymentsAPIs.url(for: .createToken(token: transactionToken)) else {
|
||||
transactionState = .transactionProcessError
|
||||
throw ProtonPlansManagerError.unableToCreateRequest
|
||||
throw TransactionHandlerError.unableToCreateRequest
|
||||
}
|
||||
debugPrint("Creating payment token..")
|
||||
do {
|
||||
|
|
@ -163,7 +181,7 @@ private extension TransactionHandler {
|
|||
|
||||
guard let request = try? paymentsAPIs.url(for: .createSubscription(newSubscription: newSub)) else {
|
||||
transactionState = .transactionProcessError
|
||||
throw ProtonPlansManagerError.unableToCreateRequest
|
||||
throw TransactionHandlerError.unableToCreateRequest
|
||||
}
|
||||
|
||||
transactionState = .createNewSubscription
|
||||
|
|
|
|||
|
|
@ -244,4 +244,17 @@ final class PaymentsAPIsTokenTests: XCTestCase {
|
|||
XCTAssertEqual(expectedResult, result?.url)
|
||||
XCTAssertNil(result?.body)
|
||||
}
|
||||
|
||||
// MARK: Transaction UUID
|
||||
|
||||
func test_transactionUUID() throws {
|
||||
|
||||
guard let expectedResult = URL(string: "https://proton.black/api/auth/v4/sessions/uuid") else {
|
||||
return
|
||||
}
|
||||
let result = try? sut.url(for: .userTransactionUUID)
|
||||
|
||||
XCTAssertEqual(expectedResult, result?.url)
|
||||
XCTAssertNil(result?.body)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import XCTest
|
|||
@testable import ProtonCorePaymentsV2
|
||||
|
||||
final class RemoteManagerTests: XCTestCase {
|
||||
|
||||
|
||||
private var paymentsAPI: PaymentsAPIs!
|
||||
private var urlSessionConfig: URLSessionConfiguration!
|
||||
private var sut: RemoteManager!
|
||||
|
|
@ -44,7 +44,7 @@ final class RemoteManagerTests: XCTestCase {
|
|||
mockRemoteManager.destroy()
|
||||
mockRemoteManager = nil
|
||||
}
|
||||
|
||||
|
||||
func XCTAssertThrowsErrorAsync<T, R>(
|
||||
_ expression: @autoclosure () async throws -> T,
|
||||
_ errorThrown: @autoclosure () -> R,
|
||||
|
|
@ -63,14 +63,14 @@ final class RemoteManagerTests: XCTestCase {
|
|||
|
||||
// MARK: Token
|
||||
extension RemoteManagerTests {
|
||||
|
||||
|
||||
func test_check_token_status_unusable_token() async throws {
|
||||
|
||||
|
||||
let mockResponse: [String: Any] = [
|
||||
"Code": 1000,
|
||||
"Status": 0
|
||||
]
|
||||
|
||||
|
||||
mockRemoteManager.setupURLSessionMock(withMockResponse: mockResponse)
|
||||
|
||||
guard let request = try? paymentsAPI.url(for: .checkToken(token: "1234214sdasd")) else {
|
||||
|
|
@ -78,12 +78,12 @@ extension RemoteManagerTests {
|
|||
return
|
||||
}
|
||||
let tokenStatus: TokenStatus = try await sut.getFromURL(request.url)
|
||||
|
||||
|
||||
XCTAssertEqual(tokenStatus.code, 1000)
|
||||
XCTAssertEqual(tokenStatus.status, 0)
|
||||
XCTAssertFalse(tokenStatus.tokenUsable)
|
||||
}
|
||||
|
||||
|
||||
func test_check_token_status_usable_token() async throws {
|
||||
|
||||
let mockResponse: [String: Any] = [
|
||||
|
|
@ -105,48 +105,48 @@ extension RemoteManagerTests {
|
|||
}
|
||||
|
||||
func test_create_payment_token() async throws {
|
||||
|
||||
|
||||
let mockResponse: [String: Any] = [
|
||||
"Code": 1000,
|
||||
"Status": 1,
|
||||
"Token": "IM_A_TOKEN",
|
||||
"Data": NSNull()
|
||||
]
|
||||
|
||||
|
||||
mockRemoteManager.setupURLSessionMock(withMockResponse: mockResponse)
|
||||
|
||||
|
||||
let token = Token(amount: 200, currency: "USD", payment: nil, paymentMethodID: nil)
|
||||
guard let request = try? paymentsAPI.url(for: .createToken(token: token)) else {
|
||||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
}
|
||||
let tokenStatus: NewToken = try await sut.postToURL(request: request)
|
||||
|
||||
|
||||
XCTAssertEqual(tokenStatus.code, 1000)
|
||||
XCTAssertEqual(tokenStatus.status, 1)
|
||||
XCTAssertEqual(tokenStatus.token, "IM_A_TOKEN")
|
||||
}
|
||||
|
||||
|
||||
func test_create_payment_token_code_only() async throws {
|
||||
|
||||
|
||||
let mockResponse: [String: Any] = [
|
||||
"Code": 1000,
|
||||
]
|
||||
|
||||
|
||||
mockRemoteManager.setupURLSessionMock(withMockResponse: mockResponse)
|
||||
|
||||
|
||||
let token = Token(amount: 200, currency: "USD", payment: nil, paymentMethodID: nil)
|
||||
guard let request = try? paymentsAPI.url(for: .createToken(token: token)) else {
|
||||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
}
|
||||
let tokenStatus: StatusResponse = try await sut.postToURL(request: request)
|
||||
|
||||
|
||||
XCTAssertEqual(tokenStatus.code, 1000)
|
||||
}
|
||||
|
||||
|
||||
func test_create_payment_token_no_response() async throws {
|
||||
|
||||
|
||||
mockRemoteManager.setupURLSessionMock()
|
||||
|
||||
let token = Token(amount: 200, currency: "USD", payment: nil, paymentMethodID: nil)
|
||||
|
|
@ -154,15 +154,14 @@ extension RemoteManagerTests {
|
|||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
try await sut.postToURL(request: request)
|
||||
XCTAssertTrue(true)
|
||||
}
|
||||
|
||||
|
||||
func test_create_payment_token_no_response_fail() async throws {
|
||||
|
||||
|
||||
let expectedErrorCode = 500
|
||||
|
||||
mockRemoteManager.setupURLSessionMock(responseStatusCode: expectedErrorCode)
|
||||
|
||||
let token = Token(amount: 200, currency: "USD", payment: nil, paymentMethodID: nil)
|
||||
|
|
@ -170,7 +169,7 @@ extension RemoteManagerTests {
|
|||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
await XCTAssertThrowsErrorAsync(
|
||||
try await sut.postToURL(request: request),
|
||||
RemoteError.responseReturnedError(errorCode: expectedErrorCode)
|
||||
|
|
@ -180,24 +179,23 @@ extension RemoteManagerTests {
|
|||
|
||||
// MARK: Subscription
|
||||
extension RemoteManagerTests {
|
||||
|
||||
|
||||
func test_get_current_Subscription() async throws {
|
||||
|
||||
|
||||
let mockResponse = Bundle.main.loadJsonDataToDic(from: "current_sub_response.json")
|
||||
mockRemoteManager.setupURLSessionMock(withMockResponse: mockResponse)
|
||||
|
||||
guard let request = try? paymentsAPI.url(for: .getCurrentSubscription) else {
|
||||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
}
|
||||
let currentSub: CurrentSubscription = try await sut.getFromURL(request.url)
|
||||
|
||||
|
||||
XCTAssertEqual(currentSub.code, 1000)
|
||||
XCTAssertEqual(currentSub.upcomingSubscriptions?[0].cycle, 1)
|
||||
}
|
||||
|
||||
|
||||
func test_create_new_Subscription() async throws {
|
||||
|
||||
|
||||
let mockResponse = Bundle.main.loadJsonDataToDic(from: "new_sub_payload.json")
|
||||
mockRemoteManager.setupURLSessionMock(withMockResponse: mockResponse)
|
||||
|
||||
|
|
@ -217,43 +215,43 @@ extension RemoteManagerTests {
|
|||
codes: ["CODE1", "CODE2"],
|
||||
couponCode: "BUNDLE2022",
|
||||
giftCode: "123abc"))
|
||||
|
||||
|
||||
guard let request = try? paymentsAPI.url(for: .createSubscription(newSubscription: payload)) else {
|
||||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
}
|
||||
let newSub: NewSubscriptionResponse = try await sut.postToURL(request: request)
|
||||
|
||||
|
||||
XCTAssertEqual(newSub.code, 1000)
|
||||
XCTAssertEqual(newSub.subscription.renew, 1)
|
||||
XCTAssertNil(newSub.upcomingSubscriptions)
|
||||
}
|
||||
|
||||
|
||||
func test_cancel_current_subscription() async throws {
|
||||
|
||||
|
||||
let mockResponse: [String: Any] = [
|
||||
"Code": 1000,
|
||||
]
|
||||
|
||||
|
||||
mockRemoteManager.setupURLSessionMock(withMockResponse: mockResponse)
|
||||
|
||||
|
||||
let payload = CancelSubscription(reason: "TEMPORARY",
|
||||
score: 7,
|
||||
context: "vpn",
|
||||
feedback: "I need a computer.",
|
||||
reasonDetails: "I do not have a computer.")
|
||||
|
||||
|
||||
guard let request = try? paymentsAPI.url(for: .cancelSubscription(cancelSubscription: payload)) else {
|
||||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
}
|
||||
let responseStatus: StatusResponse = try await sut.deleteToURL(request: request)
|
||||
|
||||
|
||||
XCTAssertEqual(responseStatus.code, 1000)
|
||||
}
|
||||
|
||||
|
||||
func test_check_subscription() async throws {
|
||||
|
||||
|
||||
let mockResponse = Bundle.main.loadJsonDataToDic(from: "check_sub_payload.json")
|
||||
mockRemoteManager.setupURLSessionMock(withMockResponse: mockResponse)
|
||||
|
||||
|
|
@ -265,89 +263,102 @@ extension RemoteManagerTests {
|
|||
codes: ["CODE1", "CODE2"],
|
||||
couponCode: "discountCode",
|
||||
giftCode: "giftCode")
|
||||
|
||||
guard let request = try? paymentsAPI.url(for: .checkSubscription(subscription: payload)) else {
|
||||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
}
|
||||
let subValidationResponse: ValidateSubscriptionResponse = try await sut.postToURL(request: request)
|
||||
|
||||
|
||||
XCTAssertEqual(subValidationResponse.code, 1000)
|
||||
XCTAssertEqual(subValidationResponse.proration, -1448)
|
||||
XCTAssertEqual(subValidationResponse.coupon.code, "TEST2022")
|
||||
}
|
||||
|
||||
|
||||
func test_latest_subscription() async throws {
|
||||
let mockResponse: [String: Any] = [
|
||||
"Code": 1000,
|
||||
"LastSubscriptionEnd": 1531519200
|
||||
]
|
||||
mockRemoteManager.setupURLSessionMock(withMockResponse: mockResponse)
|
||||
|
||||
|
||||
guard let request = try? paymentsAPI.url(for: .subscriptionLatest) else {
|
||||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
}
|
||||
let latestSub: LastSubscription = try await sut.getFromURL(request.url)
|
||||
|
||||
|
||||
XCTAssertEqual(latestSub.code, 1000)
|
||||
XCTAssertEqual(latestSub.lastSubscriptionEnd, 1531519200)
|
||||
}
|
||||
|
||||
|
||||
func test_change_renew_subscription() async throws {
|
||||
let mockResponse: [String: Any] = [
|
||||
"Code": 1000
|
||||
]
|
||||
mockRemoteManager.setupURLSessionMock(withMockResponse: mockResponse)
|
||||
|
||||
|
||||
let payload = RenewSubscription(renewalState: 1,
|
||||
cancellationFeedback: "{\n \"Reason\":\"QUALITY_ISSUE\",\n \"Feedback\":\"I need a computer.\",\n \"ReasonDetails\":\"I am Amish\",\n \"Context\":\"mail\"\n }")
|
||||
|
||||
|
||||
guard let request = try? paymentsAPI.url(for: .changeRenewSubscription(renewSubscription: payload)) else {
|
||||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
}
|
||||
let response: StatusResponse = try await sut.putToURL(request: request)
|
||||
|
||||
|
||||
XCTAssertEqual(response.code, 1000)
|
||||
}
|
||||
|
||||
func test_userTransactionUUID() async throws {
|
||||
let mockResponse: [String: Any] = [
|
||||
"Code": 1000,
|
||||
"UUID": "adq2d12dp12od1p2odmp12od"
|
||||
]
|
||||
mockRemoteManager.setupURLSessionMock(withMockResponse: mockResponse)
|
||||
|
||||
guard let request = try? paymentsAPI.url(for: .userTransactionUUID) else {
|
||||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
}
|
||||
let userTransactionUUID: UserTransactionUUIDResponse = try await sut.getFromURL(request.url)
|
||||
|
||||
XCTAssertEqual(userTransactionUUID.code, 1000)
|
||||
XCTAssertEqual(userTransactionUUID.uuid, "adq2d12dp12od1p2odmp12od")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Payment Status
|
||||
extension RemoteManagerTests {
|
||||
|
||||
|
||||
func test_payment_status() async throws {
|
||||
let mockResponse = Bundle.main.loadJsonDataToDic(from: "payment_status_payload.json")
|
||||
mockRemoteManager.setupURLSessionMock(withMockResponse: mockResponse)
|
||||
|
||||
|
||||
guard let request = try? paymentsAPI.url(for: .paymentStatus(vendor: .Apple)) else {
|
||||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
}
|
||||
let paymentStatus: PaymentsStatus = try await sut.getFromURL(request.url)
|
||||
|
||||
XCTAssertEqual(paymentStatus.vendorStates.inApp, 1)
|
||||
XCTAssertEqual(paymentStatus.vendorStates.bitcoin, 0)
|
||||
}
|
||||
}
|
||||
|
||||
extension RemoteManagerTests {
|
||||
|
||||
|
||||
func test_APIError_code_handling() async throws {
|
||||
|
||||
let mockResponse: [String: Any] = [
|
||||
"Code": 5003
|
||||
]
|
||||
mockRemoteManager.setupURLSessionMock(withMockResponse: mockResponse)
|
||||
|
||||
|
||||
let payload = RenewSubscription(renewalState: 1,
|
||||
cancellationFeedback: "dasdas")
|
||||
|
||||
guard let request = try? paymentsAPI.url(for: .changeRenewSubscription(renewSubscription: payload)) else {
|
||||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
await XCTAssertThrowsErrorAsync(
|
||||
try await sut.putToURL(request: request),
|
||||
APICodeError.badAppVersion
|
||||
|
|
@ -357,11 +368,10 @@ extension RemoteManagerTests {
|
|||
|
||||
// MARK: Plans
|
||||
extension RemoteManagerTests {
|
||||
|
||||
|
||||
func test_get_available_plans() async throws {
|
||||
let mockResponse = Bundle.main.loadJsonDataToDic(from: "availablePlans.json")
|
||||
mockRemoteManager.setupURLSessionMock(withMockResponse: mockResponse)
|
||||
|
||||
guard let request = try? paymentsAPI.url(for: .availablePlans(currency: "USD", vendor: "Apple", state: 1, timeStamp: 123124)) else {
|
||||
XCTFail("Unable to generate the expected request")
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue