From 88122fa277a94e032888a2af46cc45aa4387349b Mon Sep 17 00:00:00 2001 From: Niall Quinn Date: Tue, 6 Aug 2024 10:31:21 +0200 Subject: [PATCH] fix: Validate PENDING in resume if showSuccessCheckoutOnPendingPayment flag is true (#957) * Validate PENDING in resume if flag is true * Remove unused code * Added unit tests for CreateResumeService * Make new flag a bool * Added strings for fintechture * Remove redundant enum * Split out createresume into seperate protocol --- .../Classes/Core/Constants/Strings.swift | 12 + .../CreateResumePaymentService.swift | 28 +- .../Data Models/API/PaymentAPIModel.swift | 3 +- .../Data Models/PrimerPaymentMethodType.swift | 109 +++--- .../Classes/Modules/UserInterfaceModule.swift | 8 + .../Network/PrimerAPIClientProtocol.swift | 16 +- ...APIClientCreateResumePaymentProtocol.swift | 22 ++ .../Localizable/en.lproj/Localizable.strings | Bin 22404 -> 22728 bytes .../Localizable/es.lproj/Localizable.strings | Bin 22328 -> 22692 bytes .../Localizable/fr.lproj/Localizable.strings | Bin 22512 -> 22736 bytes .../Localizable/it.lproj/Localizable.strings | Bin 22080 -> 22420 bytes .../CreateResumePaymentServiceTests.swift | 326 ++++++++++++++++++ 12 files changed, 436 insertions(+), 88 deletions(-) create mode 100644 Sources/PrimerSDK/Classes/Services/Network/Protocols/PrimerAPIClientCreateResumePaymentProtocol.swift create mode 100644 Tests/Primer/Services/CreateResumePaymentServiceTests.swift diff --git a/Sources/PrimerSDK/Classes/Core/Constants/Strings.swift b/Sources/PrimerSDK/Classes/Core/Constants/Strings.swift index 4b6548b434..765d2160b4 100644 --- a/Sources/PrimerSDK/Classes/Core/Constants/Strings.swift +++ b/Sources/PrimerSDK/Classes/Core/Constants/Strings.swift @@ -125,6 +125,18 @@ extension Strings { value: "Confirm to pay", comment: "Confirm button title text") + static let payBySmartTransfer = NSLocalizedString("payBySmartTransfer", + tableName: nil, + bundle: Bundle.primerResources, + value: "Pay by Smart transfer", + comment: "Pay by Smart transfer button title text") + + static let payByImmediateTransfer = NSLocalizedString("payByImmediateTransfer", + tableName: nil, + bundle: Bundle.primerResources, + value: "Pay by Immediate transfer", + comment: "Pay by Immediate transfer button title text") + } } diff --git a/Sources/PrimerSDK/Classes/Core/Payment Services/CreateResumePaymentService.swift b/Sources/PrimerSDK/Classes/Core/Payment Services/CreateResumePaymentService.swift index 57c20801ee..9af6c79108 100644 --- a/Sources/PrimerSDK/Classes/Core/Payment Services/CreateResumePaymentService.swift +++ b/Sources/PrimerSDK/Classes/Core/Payment Services/CreateResumePaymentService.swift @@ -19,12 +19,12 @@ private enum CreateResumePaymentCallType: String { internal class CreateResumePaymentService: CreateResumePaymentServiceProtocol { - let apiClient: PrimerAPIClientProtocol + let apiClient: PrimerAPIClientCreateResumePaymentProtocol let paymentMethodType: String init(paymentMethodType: String, - apiClient: PrimerAPIClientProtocol = PrimerAPIClient()) { + apiClient: PrimerAPIClientCreateResumePaymentProtocol = PrimerAPIClient()) { self.paymentMethodType = paymentMethodType self.apiClient = apiClient } @@ -45,7 +45,7 @@ internal class CreateResumePaymentService: CreateResumePaymentServiceProtocol { seal.reject(error) case .success(let paymentResponse): do { - try self.validateResponse(paymentResponse: paymentResponse, callType: "create") + try self.validateResponse(paymentResponse: paymentResponse, callType: .create) seal.fulfill(paymentResponse) } catch { seal.reject(error) @@ -55,23 +55,10 @@ internal class CreateResumePaymentService: CreateResumePaymentServiceProtocol { } } - private func error(forCallType callType: CreateResumePaymentCallType) -> Error { - switch callType { - case .create: - return PrimerError.failedToCreatePayment(paymentMethodType: paymentMethodType, - description: "Failed to create payment", - userInfo: .errorUserInfoDictionary(), - diagnosticsId: UUID().uuidString) - case .resume: - return PrimerError.failedToResumePayment(paymentMethodType: paymentMethodType, - description: "Failed to resume payment", - userInfo: .errorUserInfoDictionary(), - diagnosticsId: UUID().uuidString) - } - } + private func validateResponse(paymentResponse: Response.Body.Payment, callType: CreateResumePaymentCallType) throws { - private func validateResponse(paymentResponse: Response.Body.Payment, callType: String) throws { - if paymentResponse.id == nil || paymentResponse.status == .failed { + if paymentResponse.id == nil || paymentResponse.status == .failed || + (callType == .resume && paymentResponse.status == .pending && paymentResponse.showSuccessCheckoutOnPendingPayment == false) { let err = PrimerError.paymentFailed( paymentMethodType: self.paymentMethodType, paymentId: paymentResponse.id ?? "unknown", @@ -80,7 +67,6 @@ internal class CreateResumePaymentService: CreateResumePaymentServiceProtocol { diagnosticsId: UUID().uuidString) ErrorHandler.handle(error: err) throw err - } } @@ -101,7 +87,7 @@ internal class CreateResumePaymentService: CreateResumePaymentServiceProtocol { seal.reject(error) case .success(let paymentResponse): do { - try self.validateResponse(paymentResponse: paymentResponse, callType: "resume") + try self.validateResponse(paymentResponse: paymentResponse, callType: .resume) seal.fulfill(paymentResponse) } catch { seal.reject(error) diff --git a/Sources/PrimerSDK/Classes/Data Models/API/PaymentAPIModel.swift b/Sources/PrimerSDK/Classes/Data Models/API/PaymentAPIModel.swift index 83f48431a3..3ce4cb4d1b 100644 --- a/Sources/PrimerSDK/Classes/Data Models/API/PaymentAPIModel.swift +++ b/Sources/PrimerSDK/Classes/Data Models/API/PaymentAPIModel.swift @@ -118,10 +118,11 @@ extension Response.Body { public let requiredAction: Response.Body.Payment.RequiredAction? public let status: Status public let paymentFailureReason: PrimerPaymentErrorCode.RawValue? + public var showSuccessCheckoutOnPendingPayment: Bool? = false // swiftlint:disable:next nesting public enum CodingKeys: String, CodingKey { - case id, paymentId, amount, currencyCode, customer, customerId, order, orderId, requiredAction, status, paymentFailureReason + case id, paymentId, amount, currencyCode, customer, customerId, order, orderId, requiredAction, status, paymentFailureReason, showSuccessCheckoutOnPendingPayment case dateStr = "date" } diff --git a/Sources/PrimerSDK/Classes/Data Models/PrimerPaymentMethodType.swift b/Sources/PrimerSDK/Classes/Data Models/PrimerPaymentMethodType.swift index e8d53348d7..e2b3b234f7 100644 --- a/Sources/PrimerSDK/Classes/Data Models/PrimerPaymentMethodType.swift +++ b/Sources/PrimerSDK/Classes/Data Models/PrimerPaymentMethodType.swift @@ -1,58 +1,60 @@ import Foundation internal enum PrimerPaymentMethodType: String, Codable, CaseIterable, Equatable, Hashable { - case adyenAlipay = "ADYEN_ALIPAY" - case adyenBlik = "ADYEN_BLIK" - case adyenBancontactCard = "ADYEN_BANCONTACT_CARD" - case adyenDotPay = "ADYEN_DOTPAY" - case adyenGiropay = "ADYEN_GIROPAY" - case adyenIDeal = "ADYEN_IDEAL" - case adyenInterac = "ADYEN_INTERAC" - case adyenMobilePay = "ADYEN_MOBILEPAY" - case adyenMBWay = "ADYEN_MBWAY" - case adyenMultibanco = "ADYEN_MULTIBANCO" - case adyenPayTrail = "ADYEN_PAYTRAIL" - case adyenPayshop = "ADYEN_PAYSHOP" - case adyenSofort = "ADYEN_SOFORT" - case adyenTrustly = "ADYEN_TRUSTLY" - case adyenTwint = "ADYEN_TWINT" - case adyenVipps = "ADYEN_VIPPS" - case applePay = "APPLE_PAY" - case atome = "ATOME" - case buckarooBancontact = "BUCKAROO_BANCONTACT" - case buckarooEps = "BUCKAROO_EPS" - case buckarooGiropay = "BUCKAROO_GIROPAY" - case buckarooIdeal = "BUCKAROO_IDEAL" - case buckarooSofort = "BUCKAROO_SOFORT" - case coinbase = "COINBASE" - case goCardless = "GOCARDLESS" - case googlePay = "GOOGLE_PAY" - case hoolah = "HOOLAH" - case iPay88Card = "IPAY88_CARD" - case klarna = "KLARNA" - case mollieBankcontact = "MOLLIE_BANCONTACT" - case mollieIdeal = "MOLLIE_IDEAL" - case opennode = "OPENNODE" - case payNLBancontact = "PAY_NL_BANCONTACT" - case payNLGiropay = "PAY_NL_GIROPAY" - case payNLIdeal = "PAY_NL_IDEAL" - case payNLPayconiq = "PAY_NL_PAYCONIQ" - case paymentCard = "PAYMENT_CARD" - case payPal = "PAYPAL" - case primerTestKlarna = "PRIMER_TEST_KLARNA" - case primerTestPayPal = "PRIMER_TEST_PAYPAL" - case primerTestSofort = "PRIMER_TEST_SOFORT" - case rapydFast = "RAPYD_FAST" - case rapydGCash = "RAPYD_GCASH" - case rapydGrabPay = "RAPYD_GRABPAY" - case rapydPromptPay = "RAPYD_PROMPTPAY" - case rapydPoli = "RAPYD_POLI" - case omisePromptPay = "OMISE_PROMPTPAY" - case twoCtwoP = "TWOC2P" - case xenditOvo = "XENDIT_OVO" - case xenditRetailOutlets = "XENDIT_RETAIL_OUTLETS" - case xfersPayNow = "XFERS_PAYNOW" - case nolPay = "NOL_PAY" + case adyenAlipay = "ADYEN_ALIPAY" + case adyenBlik = "ADYEN_BLIK" + case adyenBancontactCard = "ADYEN_BANCONTACT_CARD" + case adyenDotPay = "ADYEN_DOTPAY" + case adyenGiropay = "ADYEN_GIROPAY" + case adyenIDeal = "ADYEN_IDEAL" + case adyenInterac = "ADYEN_INTERAC" + case adyenMobilePay = "ADYEN_MOBILEPAY" + case adyenMBWay = "ADYEN_MBWAY" + case adyenMultibanco = "ADYEN_MULTIBANCO" + case adyenPayTrail = "ADYEN_PAYTRAIL" + case adyenPayshop = "ADYEN_PAYSHOP" + case adyenSofort = "ADYEN_SOFORT" + case adyenTrustly = "ADYEN_TRUSTLY" + case adyenTwint = "ADYEN_TWINT" + case adyenVipps = "ADYEN_VIPPS" + case applePay = "APPLE_PAY" + case atome = "ATOME" + case buckarooBancontact = "BUCKAROO_BANCONTACT" + case buckarooEps = "BUCKAROO_EPS" + case buckarooGiropay = "BUCKAROO_GIROPAY" + case buckarooIdeal = "BUCKAROO_IDEAL" + case buckarooSofort = "BUCKAROO_SOFORT" + case coinbase = "COINBASE" + case goCardless = "GOCARDLESS" + case googlePay = "GOOGLE_PAY" + case hoolah = "HOOLAH" + case iPay88Card = "IPAY88_CARD" + case klarna = "KLARNA" + case mollieBankcontact = "MOLLIE_BANCONTACT" + case mollieIdeal = "MOLLIE_IDEAL" + case opennode = "OPENNODE" + case payNLBancontact = "PAY_NL_BANCONTACT" + case payNLGiropay = "PAY_NL_GIROPAY" + case payNLIdeal = "PAY_NL_IDEAL" + case payNLPayconiq = "PAY_NL_PAYCONIQ" + case paymentCard = "PAYMENT_CARD" + case payPal = "PAYPAL" + case primerTestKlarna = "PRIMER_TEST_KLARNA" + case primerTestPayPal = "PRIMER_TEST_PAYPAL" + case primerTestSofort = "PRIMER_TEST_SOFORT" + case rapydFast = "RAPYD_FAST" + case rapydGCash = "RAPYD_GCASH" + case rapydGrabPay = "RAPYD_GRABPAY" + case rapydPromptPay = "RAPYD_PROMPTPAY" + case rapydPoli = "RAPYD_POLI" + case omisePromptPay = "OMISE_PROMPTPAY" + case twoCtwoP = "TWOC2P" + case xenditOvo = "XENDIT_OVO" + case xenditRetailOutlets = "XENDIT_RETAIL_OUTLETS" + case xfersPayNow = "XFERS_PAYNOW" + case nolPay = "NOL_PAY" + case fintechtureSmartTransfer = "FINTECTURE_SMART_TRANSFER" + case fintechtureImmediateTransfer = "FINTECHTURE_IMMEDIATE_TRANSFER" var provider: String { switch self { @@ -130,6 +132,9 @@ internal enum PrimerPaymentMethodType: String, Codable, CaseIterable, Equatable, return "XFERS" case .nolPay: return "NOL_PAY" + + case .fintechtureSmartTransfer, .fintechtureImmediateTransfer: + return "FINTECHTURE" } } } diff --git a/Sources/PrimerSDK/Classes/Modules/UserInterfaceModule.swift b/Sources/PrimerSDK/Classes/Modules/UserInterfaceModule.swift index bdd5b95a0d..53df834c28 100644 --- a/Sources/PrimerSDK/Classes/Modules/UserInterfaceModule.swift +++ b/Sources/PrimerSDK/Classes/Modules/UserInterfaceModule.swift @@ -757,6 +757,8 @@ class UserInterfaceModule: NSObject, UserInterfaceModuleProtocol { textColor: nil)) case .nolPay: return nil + case .fintechtureSmartTransfer, .fintechtureImmediateTransfer: + return nil } } @@ -801,6 +803,12 @@ class UserInterfaceModule: NSObject, UserInterfaceModuleProtocol { case PrimerPaymentMethodType.twoCtwoP.rawValue: return Strings.PaymentButton.payInInstallments + case PrimerPaymentMethodType.fintechtureSmartTransfer.rawValue: + return Strings.PaymentButton.payBySmartTransfer + + case PrimerPaymentMethodType.fintechtureImmediateTransfer.rawValue: + return Strings.PaymentButton.payByImmediateTransfer + default: return metadataButtonText } diff --git a/Sources/PrimerSDK/Classes/Services/Network/PrimerAPIClientProtocol.swift b/Sources/PrimerSDK/Classes/Services/Network/PrimerAPIClientProtocol.swift index 365c794f77..78c18ec4e3 100644 --- a/Sources/PrimerSDK/Classes/Services/Network/PrimerAPIClientProtocol.swift +++ b/Sources/PrimerSDK/Classes/Services/Network/PrimerAPIClientProtocol.swift @@ -16,7 +16,8 @@ protocol PrimerAPIClientProtocol: PrimerAPIClientBanksProtocol, PrimerAPIClientPayPalProtocol, PrimerAPIClientVaultProtocol, - PrimerAPIClientXenditProtocol { + PrimerAPIClientXenditProtocol, + PrimerAPIClientCreateResumePaymentProtocol { // MARK: Configuration @@ -91,19 +92,6 @@ protocol PrimerAPIClientProtocol: url: URL, completion: @escaping APICompletion) - // MARK: Payments - - func createPayment( - clientToken: DecodedJWTToken, - paymentRequestBody: Request.Body.Payment.Create, - completion: @escaping APICompletion) - - func resumePayment( - clientToken: DecodedJWTToken, - paymentId: String, - paymentResumeRequest: Request.Body.Payment.Resume, - completion: @escaping APICompletion) - // MARK: NolPay func fetchNolSdkSecret(clientToken: DecodedJWTToken, diff --git a/Sources/PrimerSDK/Classes/Services/Network/Protocols/PrimerAPIClientCreateResumePaymentProtocol.swift b/Sources/PrimerSDK/Classes/Services/Network/Protocols/PrimerAPIClientCreateResumePaymentProtocol.swift new file mode 100644 index 0000000000..3a23729e37 --- /dev/null +++ b/Sources/PrimerSDK/Classes/Services/Network/Protocols/PrimerAPIClientCreateResumePaymentProtocol.swift @@ -0,0 +1,22 @@ +// +// PrimerAPIClientCreateResumePaymentProtocol.swift +// PrimerSDK +// +// Created by Niall Quinn on 05/08/24. +// + +import Foundation + + +protocol PrimerAPIClientCreateResumePaymentProtocol { + func createPayment( + clientToken: DecodedJWTToken, + paymentRequestBody: Request.Body.Payment.Create, + completion: @escaping APICompletion) + + func resumePayment( + clientToken: DecodedJWTToken, + paymentId: String, + paymentResumeRequest: Request.Body.Payment.Resume, + completion: @escaping APICompletion) +} diff --git a/Sources/PrimerSDK/Resources/Localizable/en.lproj/Localizable.strings b/Sources/PrimerSDK/Resources/Localizable/en.lproj/Localizable.strings index b3334b835cefb7705a0c979de9f2ccb9b64411b9..069bb4b4b521cbaa9ec33b08a1a0f341f28e95d1 100644 GIT binary patch delta 272 zcmZo!&v;@ZBh&wXlO2uxCSPTi+L$dFK3PDTKQ@V>l0kvNlOY!fQyEeiG8qyXN`Nc{ zAXx-t=P?vBqyhOw3|b8O3|tIK3WlVoY5jsCp**>4}?`F}wxpz%(xv vXm&E#J_R6|hhl%?WIYveUx?d+fij6eb4#$8f$1iMYAkM%C*YRJ?<}PO79K=Y delta 17 ZcmZ3ok#Wa5My7xNCp+rr{fiRUJg&`9tRsv*& zFcbkG7qRq0mujGdkNR4#9+<9g<^6rP(Bf;s|1^g M{AealF0hgW0Mj@sjQ{`u delta 17 Zcmcbxk@3TNMy7xNCvOb&-xx4I8~{`?2>k#6 diff --git a/Sources/PrimerSDK/Resources/Localizable/it.lproj/Localizable.strings b/Sources/PrimerSDK/Resources/Localizable/it.lproj/Localizable.strings index 07bbbb1957c041c9e81b67080e48524cbe038306..a8608a54fc0ae9a4b5eb916ea8ae3dd5c41894a0 100644 GIT binary patch delta 278 zcmX@GhH=VzMyCJ&CN~E7P5#U*wK4l@=wv=7_R03TeBqu9xj>l8kiwA3kjPL1WGMj2 zA|N}Dp_m~J$S-2hV$f&cVo+iz0E$*JH~}%Ht`Jl`llMr;Dkm}I15L>Unv%(o3}h<+ tX;jx(5SA6M_0mh_V}@8jGD8n06+C{gMo}Art5dB?fDtZvd1#JqiE- delta 17 ZcmbQTp7FpMMy7xNCkt}=Zwz=J3IIpk2l4;_ diff --git a/Tests/Primer/Services/CreateResumePaymentServiceTests.swift b/Tests/Primer/Services/CreateResumePaymentServiceTests.swift new file mode 100644 index 0000000000..1a513d3486 --- /dev/null +++ b/Tests/Primer/Services/CreateResumePaymentServiceTests.swift @@ -0,0 +1,326 @@ +// +// File.swift +// +// +// Created by Niall Quinn on 01/08/24. +// + +import XCTest +@testable import PrimerSDK + +final class CreateResumePaymentServiceTests: XCTestCase { + + typealias Payment = Response.Body.Payment + + func test_createNoJWT() throws { + let response = Payment.successResponse + let apiClient = MockCreateResumeAPI(createResponse: response) + + let createResumeService = CreateResumePaymentService(paymentMethodType: "PAYMENT_CARD", + apiClient: apiClient) + + AppState.current.clientToken = nil + let expectation = self.expectation(description: "Promise fulfilled") + let createRequest = Request.Body.Payment.Create(token: "123") + createResumeService.createPayment(paymentRequest: createRequest).done { payment in + }.catch { error in + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func test_createSuccess() throws { + let response = Payment.successResponse + let apiClient = MockCreateResumeAPI(createResponse: response) + + let createResumeService = CreateResumePaymentService(paymentMethodType: "PAYMENT_CARD", + apiClient: apiClient) + + AppState.current.clientToken = MockAppState.mockClientToken + let expectation = self.expectation(description: "Promise fulfilled") + let createRequest = Request.Body.Payment.Create(token: "123") + createResumeService.createPayment(paymentRequest: createRequest).done { payment in + XCTAssert(payment.status == .success) + expectation.fulfill() + }.catch { error in + XCTFail("Promise rejected: \(error)") + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func test_createFailure() throws { + let response = Payment.failedStatusResponse + let apiClient = MockCreateResumeAPI(createResponse: response) + + let createResumeService = CreateResumePaymentService(paymentMethodType: "PAYMENT_CARD", + apiClient: apiClient) + + AppState.current.clientToken = MockAppState.mockClientToken + let expectation = self.expectation(description: "Promise fulfilled") + let createRequest = Request.Body.Payment.Create(token: "123") + createResumeService.createPayment(paymentRequest: createRequest).done { payment in + XCTFail("Succeeded when it should have failed") + }.catch { error in + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func test_createPending() throws { + let response = Payment.pendingStatusResponse + let apiClient = MockCreateResumeAPI(createResponse: response) + + let createResumeService = CreateResumePaymentService(paymentMethodType: "PAYMENT_CARD", + apiClient: apiClient) + + AppState.current.clientToken = MockAppState.mockClientToken + let expectation = self.expectation(description: "Promise fulfilled") + let createRequest = Request.Body.Payment.Create(token: "123") + createResumeService.createPayment(paymentRequest: createRequest).done { payment in + XCTAssert(payment.status == .pending) + expectation.fulfill() + }.catch { error in + XCTFail("Promise rejected: \(error)") + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func test_createError() throws { + let response = Payment.errorResponse + let apiClient = MockCreateResumeAPI(createResponse: response) + + let createResumeService = CreateResumePaymentService(paymentMethodType: "PAYMENT_CARD", + apiClient: apiClient) + + AppState.current.clientToken = MockAppState.mockClientToken + let expectation = self.expectation(description: "Promise fulfilled") + let createRequest = Request.Body.Payment.Create(token: "123") + createResumeService.createPayment(paymentRequest: createRequest).done { payment in + XCTFail("Succeeded when it should have failed") + }.catch { error in + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + + func test_resumeNoJWT() throws { + let response = Payment.successResponse + let apiClient = MockCreateResumeAPI(resumeResponse: response) + + let createResumeService = CreateResumePaymentService(paymentMethodType: "PAYMENT_CARD", + apiClient: apiClient) + + AppState.current.clientToken = nil + let expectation = self.expectation(description: "Promise fulfilled") + let resumeRequest = Request.Body.Payment.Resume(token: "") + createResumeService.resumePaymentWithPaymentId("", paymentResumeRequest: resumeRequest).done { payment in + }.catch { error in + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func test_resumeSuccess() throws { + let response = Payment.successResponse + let apiClient = MockCreateResumeAPI(resumeResponse: response) + + let createResumeService = CreateResumePaymentService(paymentMethodType: "PAYMENT_CARD", + apiClient: apiClient) + + AppState.current.clientToken = MockAppState.mockClientToken + let expectation = self.expectation(description: "Promise fulfilled") + let resumeRequest = Request.Body.Payment.Resume(token: "") + createResumeService.resumePaymentWithPaymentId("", paymentResumeRequest: resumeRequest).done { payment in + XCTAssert(payment.status == .success) + expectation.fulfill() + }.catch { error in + XCTFail("Promise rejected: \(error)") + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func test_resumeFailure() throws { + let response = Payment.failedStatusResponse + let apiClient = MockCreateResumeAPI(resumeResponse: response) + + let createResumeService = CreateResumePaymentService(paymentMethodType: "PAYMENT_CARD", + apiClient: apiClient) + + AppState.current.clientToken = MockAppState.mockClientToken + let expectation = self.expectation(description: "Promise fulfilled") + let resumeRequest = Request.Body.Payment.Resume(token: "") + createResumeService.resumePaymentWithPaymentId("", paymentResumeRequest: resumeRequest).done { payment in + XCTFail("Succeeded when it should have failed") + }.catch { error in + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func test_resumePending() throws { + let response = Payment.pendingStatusResponse + let apiClient = MockCreateResumeAPI(resumeResponse: response) + + let createResumeService = CreateResumePaymentService(paymentMethodType: "PAYMENT_CARD", + apiClient: apiClient) + + AppState.current.clientToken = MockAppState.mockClientToken + let expectation = self.expectation(description: "Promise fulfilled") + let resumeRequest = Request.Body.Payment.Resume(token: "") + createResumeService.resumePaymentWithPaymentId("", paymentResumeRequest: resumeRequest).done { payment in + XCTFail("Succeeded when it should have failed") + }.catch { error in + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func test_resumeError() throws { + let response = Payment.errorResponse + let apiClient = MockCreateResumeAPI(resumeResponse: response) + + let createResumeService = CreateResumePaymentService(paymentMethodType: "PAYMENT_CARD", + apiClient: apiClient) + + AppState.current.clientToken = MockAppState.mockClientToken + let expectation = self.expectation(description: "Promise fulfilled") + let resumeRequest = Request.Body.Payment.Resume(token: "") + createResumeService.resumePaymentWithPaymentId("", paymentResumeRequest: resumeRequest).done { payment in + XCTFail("Succeeded when it should have failed") + }.catch { error in + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func test_resumePending_showSuccessCheckoutOnPendingPayment() throws { + let response = Payment.pendingStatusResponseWithShowCheckoutSuccessOnPending + let apiClient = MockCreateResumeAPI(resumeResponse: response) + + let createResumeService = CreateResumePaymentService(paymentMethodType: "PAYMENT_CARD", + apiClient: apiClient) + + AppState.current.clientToken = MockAppState.mockClientToken + let expectation = self.expectation(description: "Promise fulfilled") + let resumeRequest = Request.Body.Payment.Resume(token: "") + createResumeService.resumePaymentWithPaymentId("", paymentResumeRequest: resumeRequest).done { payment in + XCTAssert(payment.status == .pending) + expectation.fulfill() + }.catch { error in + XCTFail("Failed, but flag should result in success") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + +} + + +private class MockCreateResumeAPI: PrimerAPIClientCreateResumePaymentProtocol { + + var resumeResponse: APIResult? + var createResponse: APIResult? + + init(resumeResponse: APIResult? = nil, createResponse: APIResult? = nil) { + self.resumeResponse = resumeResponse + self.createResponse = createResponse + } + + func createPayment(clientToken: DecodedJWTToken, paymentRequestBody: Request.Body.Payment.Create, completion: @escaping APICompletion) { + guard let createResponse else { + XCTFail("No create response set") + return + } + completion(createResponse) + } + + func resumePayment(clientToken: DecodedJWTToken, paymentId: String, paymentResumeRequest: Request.Body.Payment.Resume, completion: @escaping APICompletion) { + guard let resumeResponse else { + XCTFail("No resume response set") + return + } + completion(resumeResponse) + } +} + +private extension Response.Body.Payment { + static var successResponse: APIResult { + .success(.init(id: "id", + paymentId: "paymentId", + amount: 1, + currencyCode: "EUR", + customer: nil, + customerId: nil, + dateStr: nil, + order: nil, + orderId: nil, + requiredAction: nil, + status: .success, + paymentFailureReason: nil)) + } + + static var failedStatusResponse: APIResult { + .success(.init(id: "id", + paymentId: "paymentId", + amount: 1, + currencyCode: "EUR", + customer: nil, + customerId: nil, + dateStr: nil, + order: nil, + orderId: nil, + requiredAction: nil, + status: .failed, + paymentFailureReason: nil)) + } + + static var pendingStatusResponse: APIResult { + .success(.init(id: "id", + paymentId: "paymentId", + amount: 1, + currencyCode: "EUR", + customer: nil, + customerId: nil, + dateStr: nil, + order: nil, + orderId: nil, + requiredAction: nil, + status: .pending, + paymentFailureReason: nil)) + } + + static var pendingStatusResponseWithShowCheckoutSuccessOnPending: APIResult { + .success(.init(id: "id", + paymentId: "paymentId", + amount: 1, + currencyCode: "EUR", + customer: nil, + customerId: nil, + dateStr: nil, + order: nil, + orderId: nil, + requiredAction: nil, + status: .pending, + paymentFailureReason: nil, + showSuccessCheckoutOnPendingPayment: true)) + } + + static var errorResponse: APIResult { + .failure(PrimerError.failedToCreatePayment(paymentMethodType: "PAYMENT_CARD", + description: "", + userInfo: [:], + diagnosticsId: "")) + } +}