diff --git a/.cz.toml b/.cz.toml index 55670a6a6..81c086da1 100644 --- a/.cz.toml +++ b/.cz.toml @@ -1,6 +1,6 @@ [tool.commitizen] version_scheme = "semver" -version = "2.31.0" +version = "2.31.1" version_files = [ "Sources/PrimerSDK/Classes/version.swift:let PrimerSDKVersion", "PrimerSDK.podspec:s.version" diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eaf767d1..c2f694261 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 2.31.1 (2024-10-04) + +### Fix + +- Apple Pay incorrect timeout reporting (#1010) +- Add additional rails to guard against crashes (#1009) +- Fallback to Web flow when Vipps app is not installed (#1008) +- Fix error reporting for apple pay display failure (#997) + ## 2.31.0 (2024-08-29) ### Feat diff --git a/Debug App/Sources/View Controllers/MerchantSessionAndSettingsViewController.swift b/Debug App/Sources/View Controllers/MerchantSessionAndSettingsViewController.swift index 9afa006ab..78888fea2 100644 --- a/Debug App/Sources/View Controllers/MerchantSessionAndSettingsViewController.swift +++ b/Debug App/Sources/View Controllers/MerchantSessionAndSettingsViewController.swift @@ -163,7 +163,7 @@ class MerchantSessionAndSettingsViewController: UIViewController { //Below are gated by applePayCaptureShippingDetails, default to on when above is true var applePayCaptureShippingAddress = true var applePayRequireShippingMethod = true - var applePayAdditionalContactFields: [PrimerApplePayOptions.ShippingOptions.AdditionalShippingContactField]? = [.name, .emailAddress, .phoneNumber] +// var applePayAdditionalContactFields: [PrimerApplePayOptions.ShippingOptions.AdditionalShippingContactField]? = [.name, .emailAddress, .phoneNumber] func setAccessibilityIds() { self.view.accessibilityIdentifier = "Background View" @@ -429,18 +429,18 @@ class MerchantSessionAndSettingsViewController: UIViewController { } @IBAction func applePayShippingContactNameSwitchChanged(_ sender: UISwitch) { - if sender.isOn { - var fields = applePayAdditionalContactFields ?? [] - if !fields.contains(.name) { - fields.append(.name) - } - applePayAdditionalContactFields = fields - } else { - applePayAdditionalContactFields?.removeAll(where: { $0 == .name }) - if applePayAdditionalContactFields?.isEmpty == true { - applePayAdditionalContactFields = nil - } - } +// if sender.isOn { +// var fields = applePayAdditionalContactFields ?? [] +// if !fields.contains(.name) { +// fields.append(.name) +// } +// applePayAdditionalContactFields = fields +// } else { +// applePayAdditionalContactFields?.removeAll(where: { $0 == .name }) +// if applePayAdditionalContactFields?.isEmpty == true { +// applePayAdditionalContactFields = nil +// } +// } } diff --git a/PrimerSDK.podspec b/PrimerSDK.podspec index d023e5b22..aff8c7cf4 100644 --- a/PrimerSDK.podspec +++ b/PrimerSDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "PrimerSDK" - s.version = "2.31.0" + s.version = "2.31.1" s.summary = "Official iOS SDK for Primer" s.description = <<-DESC This library contains the official iOS SDK for Primer. Install this Cocoapod to seemlessly integrate the Primer Checkout & API platform in your app. diff --git a/Sources/PrimerSDK/Classes/version.swift b/Sources/PrimerSDK/Classes/version.swift index 1a8b85f3b..b38c975ab 100644 --- a/Sources/PrimerSDK/Classes/version.swift +++ b/Sources/PrimerSDK/Classes/version.swift @@ -1,2 +1,2 @@ // swiftlint:disable:next identifier_name -public let PrimerSDKVersion = "2.31.0" +public let PrimerSDKVersion = "2.31.1" diff --git a/Tests/Primer/ApplePay/ApplePayPresentationManagerTests.swift b/Tests/Primer/ApplePay/ApplePayPresentationManagerTests.swift index a1e39389c..987ff1ff5 100644 --- a/Tests/Primer/ApplePay/ApplePayPresentationManagerTests.swift +++ b/Tests/Primer/ApplePay/ApplePayPresentationManagerTests.swift @@ -56,28 +56,28 @@ final class ApplePayPresentationManagerTests: XCTestCase { XCTAssertTrue(sut.isPresentable) } - func testShippingContactFields() throws { - let additionalFields: [PrimerApplePayOptions.ShippingOptions.AdditionalShippingContactField] = [.name, .emailAddress, .phoneNumber] - - var applePayOptions = PrimerApplePayOptions(merchantIdentifier: "merchant_id", - merchantName: "merchant_name", - checkProvidedNetworks: true, - shippingOptions: .init(isCaptureShippingAddressEnabled: true, - additionalShippingContactFields: additionalFields, requireShippingMethod: true)) - var shippingFields = sut.shippingContactFields(applePayOptions: applePayOptions) - - XCTAssertEqual(shippingFields, [.name, .postalAddress, .emailAddress, .phoneNumber]) - - applePayOptions = PrimerApplePayOptions(merchantIdentifier: "merchant_id", - merchantName: "merchant_name", - checkProvidedNetworks: true, - shippingOptions: .init(isCaptureShippingAddressEnabled: true, - additionalShippingContactFields: nil, requireShippingMethod: true)) - - shippingFields = sut.shippingContactFields(applePayOptions: applePayOptions) - - XCTAssertEqual(shippingFields, [.postalAddress]) - } +// func testShippingContactFields() throws { +// let additionalFields: [PrimerApplePayOptions.ShippingOptions.AdditionalShippingContactField] = [.name, .emailAddress, .phoneNumber] +// +// var applePayOptions = PrimerApplePayOptions(merchantIdentifier: "merchant_id", +// merchantName: "merchant_name", +// checkProvidedNetworks: true, +// shippingOptions: .init(isCaptureShippingAddressEnabled: true, +// additionalShippingContactFields: additionalFields, requireShippingMethod: true)) +// var shippingFields = sut.shippingContactFields(applePayOptions: applePayOptions) +// +// XCTAssertEqual(shippingFields, [.name, .postalAddress, .emailAddress, .phoneNumber]) +// +// applePayOptions = PrimerApplePayOptions(merchantIdentifier: "merchant_id", +// merchantName: "merchant_name", +// checkProvidedNetworks: true, +// shippingOptions: .init(isCaptureShippingAddressEnabled: true, +// additionalShippingContactFields: nil, requireShippingMethod: true)) +// +// shippingFields = sut.shippingContactFields(applePayOptions: applePayOptions) +// +// XCTAssertEqual(shippingFields, [.postalAddress]) +// } func testErrorForDisplay() { let error = sut.errorForDisplay diff --git a/Tests/Primer/Tokenization/View Models/ApplePayTokenizationViewModelTests.swift b/Tests/Primer/Tokenization/View Models/ApplePayTokenizationViewModelTests.swift index 8f140e2f7..89ed48f71 100644 --- a/Tests/Primer/Tokenization/View Models/ApplePayTokenizationViewModelTests.swift +++ b/Tests/Primer/Tokenization/View Models/ApplePayTokenizationViewModelTests.swift @@ -418,198 +418,198 @@ final class ApplePayTokenizationViewModelTests: XCTestCase { } } - func testProcessShippingContactChange() async throws { - let contact = PKContact() - var nameParts = PersonNameComponents() - nameParts.givenName = "John" - nameParts.familyName = "Doe" - contact.name = nameParts - - contact.phoneNumber = CNPhoneNumber(stringValue: "1234567890") - - contact.emailAddress = "john.doe@example.com" - - let address = CNMutablePostalAddress() - address.street = "123 Apple Street" - address.city = "Cupertino" - address.state = "CA" - address.postalCode = "95014" - address.country = "United States" - contact.postalAddress = address - - let apiClient = MockPrimerAPIClient() - PrimerAPIConfigurationModule.apiClient = apiClient - - - guard var config = PrimerAPIConfiguration.current else { - XCTFail("Unable to generate configuration") - return - } - config.checkoutModules = checkoutModules - - config.clientSession = ClientSession.APIResponse( - clientSessionId: nil, - paymentMethod: nil, - order: .init(id: "OrderId", - merchantAmount: nil, - totalOrderAmount: 1200, - totalTaxAmount: nil, - countryCode: .init(rawValue: "GB"), - currencyCode: .init(code: "GBP", decimalDigits: 2), - fees: nil, - lineItems: [ - .init(itemId: "123", - quantity: 1, - amount: 1000, - discountAmount: nil, - name: "Fancy Shoes", - description: "Some nice shoes", - taxAmount: nil, - taxCode: nil, - productType: nil) - ], - shippingMethod: - ClientSession.Order.ShippingMethod(amount: 200, - methodId: "Shipping", - methodName: "Shipping", - methodDescription: "Description") - ), - customer: nil, - testId: nil) - - - let sut = ApplePayTokenizationViewModel(config: PrimerPaymentMethod(id: "APPLE_PAY", - implementationType: .nativeSdk, - type: "APPLE_PAY", - name: "Apple Pay", - processorConfigId: nil, - surcharge: nil, - options: nil, - displayMetadata: nil)) - - apiClient.fetchConfigurationWithActionsResult = (config, nil) - PrimerAPIConfigurationModule.apiConfiguration = config - - // Test happy path - let update = await sut.processShippingContactChange(contact) - - XCTAssertNotNil(update.paymentSummaryItems) - XCTAssertNotNil(update.shippingMethods) - - //Test error when no Address - contact.postalAddress = nil - let update2 = await sut.processShippingContactChange(contact) - XCTAssertNotNil(update2.errors) - - //Test Error when no shipping methods and Settings requireShippingMethod - let settings = PrimerSettings(paymentMethodOptions: - PrimerPaymentMethodOptions(applePayOptions: - PrimerApplePayOptions(merchantIdentifier: "merchant_id", merchantName: "merchant_name", shippingOptions: .init(isCaptureShippingAddressEnabled: true, requireShippingMethod: true)) - ) - ) - DependencyContainer.register(settings as PrimerSettingsProtocol) - - contact.postalAddress = address - config.checkoutModules = nil - apiClient.fetchConfigurationWithActionsResult = (config, nil) - PrimerAPIConfigurationModule.apiConfiguration = config - - let update3 = await sut.processShippingContactChange(contact) - XCTAssertNotNil(update3.errors) - - //Test error when no ClientSession - config.clientSession = nil - apiClient.fetchConfigurationWithActionsResult = (config, nil) - PrimerAPIConfigurationModule.apiConfiguration = config - - let update4 = await sut.processShippingContactChange(contact) - XCTAssertNotNil(update4.errors) - } - - func testProcessShippingMethodChange() async throws { - let sut = ApplePayTokenizationViewModel(config: PrimerPaymentMethod(id: "APPLE_PAY", - implementationType: .nativeSdk, - type: "APPLE_PAY", - name: "Apple Pay", - processorConfigId: nil, - surcharge: nil, - options: nil, - displayMetadata: nil)) - - //Test shipping method with no ID results in empty update - let shippingMethod = PKShippingMethod() - let update = await sut.processShippingMethodChange(shippingMethod) - XCTAssert(update.paymentSummaryItems.isEmpty) - - //Test no clientSession results in empty update - shippingMethod.identifier = "123" - let update2 = await sut.processShippingMethodChange(shippingMethod) - XCTAssert(update2.paymentSummaryItems.isEmpty) - - guard var config = PrimerAPIConfiguration.current else { - XCTFail("Unable to generate configuration") - return - } - config.checkoutModules = checkoutModules - - config.clientSession = ClientSession.APIResponse( - clientSessionId: nil, - paymentMethod: nil, - order: .init(id: "OrderId", - merchantAmount: nil, - totalOrderAmount: 1200, - totalTaxAmount: nil, - countryCode: .init(rawValue: "GB"), - currencyCode: .init(code: "GBP", decimalDigits: 2), - fees: nil, - lineItems: [ - .init(itemId: "123", - quantity: 1, - amount: 1000, - discountAmount: nil, - name: "Fancy Shoes", - description: "Some nice shoes", - taxAmount: nil, - taxCode: nil, - productType: nil) - ], - shippingMethod: - ClientSession.Order.ShippingMethod(amount: 200, - methodId: "Shipping", - methodName: "Shipping", - methodDescription: "Description") - ), - customer: nil, - testId: nil) - - let apiClient = MockPrimerAPIClient() - PrimerAPIConfigurationModule.apiClient = apiClient - - config.checkoutModules = [Response.Body.Configuration.CheckoutModule(type: "SHIPPING", - requestUrlStr: nil, - options: ShippingMethodOptions(shippingMethods: [ - ShippingMethod(name: "Default", - description: "The default method", - amount: 100, - id: "default"), - ShippingMethod(name: "Next Day", - description: "Get your stuff next day", - amount: 200, - id: "nextDay")], - selectedShippingMethod: "nextDay") - )] - apiClient.fetchConfigurationWithActionsResult = (config, nil) - PrimerAPIConfigurationModule.apiConfiguration = config - - let shippingMethod2 = PKShippingMethod(label: "Next Day", amount: 200) - shippingMethod2.identifier = "nextDay" - - let update3 = await sut.processShippingMethodChange(shippingMethod2) - XCTAssert(update3.paymentSummaryItems.count == 3) - let shippingItem = update3.paymentSummaryItems[1] - XCTAssertEqual(shippingItem.amount, 2) - XCTAssertEqual(shippingItem.label, "Shipping") - } +// func testProcessShippingContactChange() async throws { +// let contact = PKContact() +// var nameParts = PersonNameComponents() +// nameParts.givenName = "John" +// nameParts.familyName = "Doe" +// contact.name = nameParts +// +// contact.phoneNumber = CNPhoneNumber(stringValue: "1234567890") +// +// contact.emailAddress = "john.doe@example.com" +// +// let address = CNMutablePostalAddress() +// address.street = "123 Apple Street" +// address.city = "Cupertino" +// address.state = "CA" +// address.postalCode = "95014" +// address.country = "United States" +// contact.postalAddress = address +// +// let apiClient = MockPrimerAPIClient() +// PrimerAPIConfigurationModule.apiClient = apiClient +// +// +// guard var config = PrimerAPIConfiguration.current else { +// XCTFail("Unable to generate configuration") +// return +// } +// config.checkoutModules = checkoutModules +// +// config.clientSession = ClientSession.APIResponse( +// clientSessionId: nil, +// paymentMethod: nil, +// order: .init(id: "OrderId", +// merchantAmount: nil, +// totalOrderAmount: 1200, +// totalTaxAmount: nil, +// countryCode: .init(rawValue: "GB"), +// currencyCode: .init(code: "GBP", decimalDigits: 2), +// fees: nil, +// lineItems: [ +// .init(itemId: "123", +// quantity: 1, +// amount: 1000, +// discountAmount: nil, +// name: "Fancy Shoes", +// description: "Some nice shoes", +// taxAmount: nil, +// taxCode: nil, +// productType: nil) +// ], +// shippingMethod: +// ClientSession.Order.ShippingMethod(amount: 200, +// methodId: "Shipping", +// methodName: "Shipping", +// methodDescription: "Description") +// ), +// customer: nil, +// testId: nil) +// +// +// let sut = ApplePayTokenizationViewModel(config: PrimerPaymentMethod(id: "APPLE_PAY", +// implementationType: .nativeSdk, +// type: "APPLE_PAY", +// name: "Apple Pay", +// processorConfigId: nil, +// surcharge: nil, +// options: nil, +// displayMetadata: nil)) +// +// apiClient.fetchConfigurationWithActionsResult = (config, nil) +// PrimerAPIConfigurationModule.apiConfiguration = config +// +// // Test happy path +// let update = await sut.processShippingContactChange(contact) +// +// XCTAssertNotNil(update.paymentSummaryItems) +// XCTAssertNotNil(update.shippingMethods) +// +// //Test error when no Address +// contact.postalAddress = nil +// let update2 = await sut.processShippingContactChange(contact) +// XCTAssertNotNil(update2.errors) +// +// //Test Error when no shipping methods and Settings requireShippingMethod +// let settings = PrimerSettings(paymentMethodOptions: +// PrimerPaymentMethodOptions(applePayOptions: +// PrimerApplePayOptions(merchantIdentifier: "merchant_id", merchantName: "merchant_name", shippingOptions: .init(isCaptureShippingAddressEnabled: true, requireShippingMethod: true)) +// ) +// ) +// DependencyContainer.register(settings as PrimerSettingsProtocol) +// +// contact.postalAddress = address +// config.checkoutModules = nil +// apiClient.fetchConfigurationWithActionsResult = (config, nil) +// PrimerAPIConfigurationModule.apiConfiguration = config +// +// let update3 = await sut.processShippingContactChange(contact) +// XCTAssertNotNil(update3.errors) +// +// //Test error when no ClientSession +// config.clientSession = nil +// apiClient.fetchConfigurationWithActionsResult = (config, nil) +// PrimerAPIConfigurationModule.apiConfiguration = config +// +// let update4 = await sut.processShippingContactChange(contact) +// XCTAssertNotNil(update4.errors) +// } + +// func testProcessShippingMethodChange() async throws { +// let sut = ApplePayTokenizationViewModel(config: PrimerPaymentMethod(id: "APPLE_PAY", +// implementationType: .nativeSdk, +// type: "APPLE_PAY", +// name: "Apple Pay", +// processorConfigId: nil, +// surcharge: nil, +// options: nil, +// displayMetadata: nil)) +// +// //Test shipping method with no ID results in empty update +// let shippingMethod = PKShippingMethod() +// let update = await sut.processShippingMethodChange(shippingMethod) +// XCTAssert(update.paymentSummaryItems.isEmpty) +// +// //Test no clientSession results in empty update +// shippingMethod.identifier = "123" +// let update2 = await sut.processShippingMethodChange(shippingMethod) +// XCTAssert(update2.paymentSummaryItems.isEmpty) +// +// guard var config = PrimerAPIConfiguration.current else { +// XCTFail("Unable to generate configuration") +// return +// } +// config.checkoutModules = checkoutModules +// +// config.clientSession = ClientSession.APIResponse( +// clientSessionId: nil, +// paymentMethod: nil, +// order: .init(id: "OrderId", +// merchantAmount: nil, +// totalOrderAmount: 1200, +// totalTaxAmount: nil, +// countryCode: .init(rawValue: "GB"), +// currencyCode: .init(code: "GBP", decimalDigits: 2), +// fees: nil, +// lineItems: [ +// .init(itemId: "123", +// quantity: 1, +// amount: 1000, +// discountAmount: nil, +// name: "Fancy Shoes", +// description: "Some nice shoes", +// taxAmount: nil, +// taxCode: nil, +// productType: nil) +// ], +// shippingMethod: +// ClientSession.Order.ShippingMethod(amount: 200, +// methodId: "Shipping", +// methodName: "Shipping", +// methodDescription: "Description") +// ), +// customer: nil, +// testId: nil) +// +// let apiClient = MockPrimerAPIClient() +// PrimerAPIConfigurationModule.apiClient = apiClient +// +// config.checkoutModules = [Response.Body.Configuration.CheckoutModule(type: "SHIPPING", +// requestUrlStr: nil, +// options: ShippingMethodOptions(shippingMethods: [ +// ShippingMethod(name: "Default", +// description: "The default method", +// amount: 100, +// id: "default"), +// ShippingMethod(name: "Next Day", +// description: "Get your stuff next day", +// amount: 200, +// id: "nextDay")], +// selectedShippingMethod: "nextDay") +// )] +// apiClient.fetchConfigurationWithActionsResult = (config, nil) +// PrimerAPIConfigurationModule.apiConfiguration = config +// +// let shippingMethod2 = PKShippingMethod(label: "Next Day", amount: 200) +// shippingMethod2.identifier = "nextDay" +// +// let update3 = await sut.processShippingMethodChange(shippingMethod2) +// XCTAssert(update3.paymentSummaryItems.count == 3) +// let shippingItem = update3.paymentSummaryItems[1] +// XCTAssertEqual(shippingItem.amount, 2) +// XCTAssertEqual(shippingItem.label, "Shipping") +// } // MARK: Helpers