260 lines
10 KiB
Swift
260 lines
10 KiB
Swift
//
|
|
// STPPaymentHandlerTests.swift
|
|
// StripeiOS Tests
|
|
//
|
|
// Created by Cameron Sabol on 3/8/21.
|
|
// Copyright © 2021 Stripe, Inc. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import OHHTTPStubs
|
|
import OHHTTPStubsSwift
|
|
import StripeCoreTestUtils
|
|
import XCTest
|
|
|
|
@testable@_spi(STP) import Stripe
|
|
@testable@_spi(STP) import Stripe3DS2
|
|
@testable@_spi(STP) import StripeCore
|
|
@testable@_spi(STP) import StripePayments
|
|
@testable@_spi(STP) import StripePaymentSheet
|
|
@testable@_spi(STP) import StripePaymentsUI
|
|
|
|
class STPPaymentHandlerStubbedTests: STPNetworkStubbingTestCase {
|
|
override func setUp() {
|
|
self.recordingMode = false
|
|
super.setUp()
|
|
}
|
|
|
|
func testCanPresentErrorsAreReported() {
|
|
let createPaymentIntentExpectation = expectation(
|
|
description: "createPaymentIntentExpectation"
|
|
)
|
|
var retrievedClientSecret: String?
|
|
STPTestingAPIClient.shared().createPaymentIntent(withParams: nil) {
|
|
(createdPIClientSecret, _) in
|
|
if let createdPIClientSecret = createdPIClientSecret {
|
|
retrievedClientSecret = createdPIClientSecret
|
|
createPaymentIntentExpectation.fulfill()
|
|
} else {
|
|
XCTFail()
|
|
}
|
|
}
|
|
wait(for: [createPaymentIntentExpectation], timeout: 8) // STPTestingNetworkRequestTimeout
|
|
guard let clientSecret = retrievedClientSecret,
|
|
let currentYear = Calendar.current.dateComponents([.year], from: Date()).year
|
|
else {
|
|
XCTFail()
|
|
return
|
|
}
|
|
|
|
let expiryYear = NSNumber(value: currentYear + 2)
|
|
let expiryMonth = NSNumber(1)
|
|
|
|
let cardParams = STPPaymentMethodCardParams()
|
|
cardParams.number = "4000000000003220"
|
|
cardParams.expYear = expiryYear
|
|
cardParams.expMonth = expiryMonth
|
|
cardParams.cvc = "123"
|
|
|
|
let address = STPPaymentMethodAddress()
|
|
address.postalCode = "12345"
|
|
let billingDetails = STPPaymentMethodBillingDetails()
|
|
billingDetails.address = address
|
|
|
|
let paymentMethodParams = STPPaymentMethodParams.paramsWith(
|
|
card: cardParams,
|
|
billingDetails: billingDetails,
|
|
metadata: nil
|
|
)
|
|
|
|
let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret)
|
|
paymentIntentParams.paymentMethodParams = paymentMethodParams
|
|
|
|
// STPTestingDefaultPublishableKey
|
|
STPAPIClient.shared.publishableKey = "pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6"
|
|
|
|
let paymentHandlerExpectation = expectation(description: "paymentHandlerExpectation")
|
|
STPPaymentHandler.shared().checkCanPresentInTest = true
|
|
STPPaymentHandler.shared().confirmPayment(paymentIntentParams, with: self) {
|
|
(status, paymentIntent, error) in
|
|
XCTAssertTrue(status == .failed)
|
|
XCTAssertNotNil(paymentIntent)
|
|
XCTAssertNotNil(error)
|
|
XCTAssertEqual(
|
|
error?.userInfo[STPError.errorMessageKey] as? String,
|
|
"authenticationPresentingViewController is not in the window hierarchy. You should probably return the top-most view controller instead."
|
|
)
|
|
paymentHandlerExpectation.fulfill()
|
|
}
|
|
// 2*STPTestingNetworkRequestTimeout payment handler needs to make an ares for this
|
|
// test in addition to fetching the payment intent
|
|
wait(for: [paymentHandlerExpectation], timeout: 2 * 8)
|
|
}
|
|
}
|
|
|
|
class STPPaymentHandlerTests: APIStubbedTestCase {
|
|
|
|
func testPaymentHandlerRetriesWithBackoff() {
|
|
STPPaymentHandler.sharedHandler.apiClient = stubbedAPIClient()
|
|
|
|
stub { urlRequest in
|
|
return urlRequest.url?.absoluteString.contains("3ds2/authenticate") ?? false
|
|
} response: { _ in
|
|
let jsonText = """
|
|
{
|
|
"state": "challenge_required",
|
|
"livemode": "false",
|
|
"ares" : {
|
|
"dsTransID": "4e4750e7-6ab5-45a4-accf-9c668ed3b5a7",
|
|
"acsTransID": "fa695a82-a48c-455d-9566-a652058dda27",
|
|
"p_messageVersion": "1.0.5",
|
|
"acsOperatorID": "acsOperatorUL",
|
|
"sdkTransID": "D77EB83F-F317-4E29-9852-EBAAB55515B7",
|
|
"eci": "00",
|
|
"dsReferenceNumber": "3DS_LOA_DIS_PPFU_020100_00010",
|
|
"acsReferenceNumber": "3DS_LOA_ACS_PPFU_020100_00009",
|
|
"threeDSServerTransID": "fc7a39de-dc41-4b65-ba76-a322769b2efc",
|
|
"messageVersion": "2.1.0",
|
|
"authenticationValue": "AABBCCDDEEFFAABBCCDDEEFFAAA=",
|
|
"messageType": "pArs",
|
|
"transStatus": "C",
|
|
"acsChallengeMandated": "NO"
|
|
}
|
|
}
|
|
"""
|
|
return HTTPStubsResponse(
|
|
data: jsonText.data(using: .utf8)!,
|
|
statusCode: 200,
|
|
headers: nil
|
|
)
|
|
}
|
|
|
|
stub { urlRequest in
|
|
return urlRequest.url?.absoluteString.contains("3ds2/challenge_complete") ?? false
|
|
} response: { _ in
|
|
let errorResponse = [
|
|
"error":
|
|
[
|
|
"message": "This is intentionally failing for this test.",
|
|
"type": "invalid_request_error",
|
|
],
|
|
]
|
|
return HTTPStubsResponse(jsonObject: errorResponse, statusCode: 400, headers: nil)
|
|
}
|
|
|
|
let paymentHandlerExpectation = expectation(
|
|
description: "paymentHandlerFinished"
|
|
)
|
|
var inProgress = true
|
|
|
|
// Meaningless cert, generated for this test
|
|
// Expires 3/2/2121: Apologies to future engineers!
|
|
let cert = """
|
|
MIIBijCB9AIBATANBgkqhkiG9w0BAQUFADANMQswCQYDVQQGEwJVUzAgFw0yMTAz
|
|
MjYxODQyNDVaGA8yMTIxMDMwMjE4NDI0NVowDTELMAkGA1UEBhMCVVMwgZ8wDQYJ
|
|
KoZIhvcNAQEBBQADgY0AMIGJAoGBAL6rIW6t+8eo1exqhvYt8H1vM+TyHNNychlD
|
|
hILw745yXZQAy9ByRG3euYEydE3SFINgWBCUuwWmkNfsZUW7Uci1PBMglBFHJrE8
|
|
8ZvtuJgnPkqmu97a9JkyROiaqAmqoMDP95HiZG5i3a1E/QPpPyYA3VJ/El17Qqkl
|
|
aHN32qzjAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAUhxbGQ5sQMDUqFTvibU7RzqL
|
|
dTaFhdjTDBu5YeIbXXUrJSG2AydXRq7OacRksnQhvNYXimfcgfse46XQG7rKUCfj
|
|
kbazRiRxMZylTz8zbePAFcVq6zxJ+RBVrv51D+/JgbCcQ50nZiocllR0J9UL8CKZ
|
|
obaUC2OjBbSuCZwF8Ig=
|
|
"""
|
|
let rootCA = """
|
|
MIIBkDCB+gIJAJ3pmjFOkxTXMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNVBAYTAlVT
|
|
MB4XDTIxMDMyNjE4NDEzMVoXDTIyMDMyNjE4NDEzMVowDTELMAkGA1UEBhMCVVMw
|
|
gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKmFDGPV77Fk/wgUMwbxjQk+bpUY
|
|
cTjNBsjK3xMaUWeE17Sry6IguO1iWaXVey9YJ1Dm83PNO/5i9nHh3gmFhEJmc55T
|
|
g+0tZQigjTcs5/BfmWtrfPYIWqKvIJqkkHrIEJnwavAS5OFGyDArHLwUtsgJbDmW
|
|
tIeQg3EH/8BSWR0BAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEATY2aQvZZJLPgUr1/
|
|
oDvRy6KZ6p7n3+jXF8DNvVOIaQRD4Ndk5NfStteIT5XvzfmD6QqpG3nlJ6Wy3oSP
|
|
03KvO4GWIyP9cuP/QLaEmxJIYKwPrdxLkUHFfzyy8tN54xOWPxN4Up9gVN6pSdVk
|
|
KWrsPfhPs3G57wir370Q69lV/8A=
|
|
"""
|
|
let iauss = STPIntentActionUseStripeSDK(
|
|
encryptionInfo: [
|
|
"certificate": cert,
|
|
"directory_server_id": "0000000000",
|
|
"root_certificate_authorities": [rootCA],
|
|
],
|
|
directoryServerName: "none",
|
|
directoryServerKeyID: "none",
|
|
serverTransactionID: "none",
|
|
threeDSSourceID: "none",
|
|
publishableKeyOverride: nil,
|
|
threeDS2IntentOverride: nil,
|
|
allResponseFields: [:]
|
|
)
|
|
let action = STPIntentAction(
|
|
type: .useStripeSDK,
|
|
redirectToURL: nil,
|
|
alipayHandleRedirect: nil,
|
|
useStripeSDK: iauss,
|
|
oxxoDisplayDetails: nil,
|
|
weChatPayRedirectToApp: nil,
|
|
boletoDisplayDetails: nil,
|
|
verifyWithMicrodeposits: nil,
|
|
cashAppRedirectToApp: nil,
|
|
allResponseFields: [:]
|
|
)
|
|
let setupIntent = STPSetupIntent(
|
|
stripeID: "test",
|
|
clientSecret: "test",
|
|
created: Date(),
|
|
countryCode: "US",
|
|
customerID: nil,
|
|
stripeDescription: nil,
|
|
linkSettings: nil,
|
|
livemode: false,
|
|
nextAction: action,
|
|
orderedPaymentMethodTypes: [],
|
|
paymentMethodID: "test",
|
|
paymentMethod: nil,
|
|
paymentMethodOptions: nil,
|
|
paymentMethodTypes: [],
|
|
status: .requiresAction,
|
|
usage: .none,
|
|
lastSetupError: nil,
|
|
allResponseFields: [:],
|
|
unactivatedPaymentMethodTypes: []
|
|
)
|
|
|
|
// We expect this request to retry a few times with exponential backoff before calling the completion handler.
|
|
STPPaymentHandler.sharedHandler._handleNextAction(
|
|
for: setupIntent,
|
|
with: self,
|
|
returnURL: nil
|
|
) { (status, _, _) in
|
|
XCTAssertEqual(status, .failed)
|
|
inProgress = false
|
|
paymentHandlerExpectation.fulfill()
|
|
}
|
|
|
|
let checkedStillInProgress = expectation(
|
|
description: "Checked that we're still in progress after 2s"
|
|
)
|
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) {
|
|
// Make sure we're still in progress after 2 seconds
|
|
// This shows that we're retrying the 3DS2 request a few times
|
|
// while applying an appropriate amount of backoff.
|
|
XCTAssertEqual(inProgress, true)
|
|
checkedStillInProgress.fulfill()
|
|
}
|
|
|
|
wait(for: [paymentHandlerExpectation, checkedStillInProgress], timeout: 30)
|
|
STPPaymentHandler.sharedHandler.apiClient = STPAPIClient.shared
|
|
}
|
|
}
|
|
|
|
extension STPPaymentHandlerTests: STPAuthenticationContext {
|
|
func authenticationPresentingViewController() -> UIViewController {
|
|
return UIViewController()
|
|
}
|
|
}
|
|
|
|
extension STPPaymentHandlerStubbedTests: STPAuthenticationContext {
|
|
func authenticationPresentingViewController() -> UIViewController {
|
|
return UIViewController()
|
|
}
|
|
}
|