Skip to content

Commit

Permalink
feat: Expose ApplePay Shipping Options and align them with Web SDK (#…
Browse files Browse the repository at this point in the history
…1049)

* Renaming, uncommenting and removal of unused fields

* Add BillingOptions

* Add UI to debug app

* Implement BillingOptions

* Update tests

* Stop adding .name when .postalAddress is passed

* Fix tests

---------

Co-authored-by: Boris Nikolic <[email protected]>
  • Loading branch information
borisprimer and BorisNikolic authored Dec 16, 2024
1 parent b75b525 commit 443e6c7
Show file tree
Hide file tree
Showing 6 changed files with 590 additions and 253 deletions.
127 changes: 100 additions & 27 deletions Debug App/Resources/Localized Views/Base.lproj/Main.storyboard

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,19 @@ class MerchantSessionAndSettingsViewController: UIViewController {
@IBOutlet weak var applePayCaptureBillingAddressSwitch: UISwitch!
@IBOutlet weak var applePayCheckProvidedNetworksSwitch: UISwitch!

@IBOutlet weak var applePayBillingControlStackView: UIStackView!
@IBOutlet weak var applePayBillingContactNameSwitch: UISwitch!
@IBOutlet weak var applePayBillingContactEmailSwitch: UISwitch!
@IBOutlet weak var applePayBillingContactPhoneSwitch: UISwitch!
@IBOutlet weak var applePayBillingContactPostalAddressSwitch: UISwitch!

@IBOutlet weak var applePayShippingControlStackView: UIStackView!
@IBOutlet weak var applePayShippingDetailsSwitch: UISwitch!
@IBOutlet weak var applePayCaptureShippingAddressEnabledSwitch: UISwitch!
@IBOutlet weak var applePayRequireShippingMethodSwitch: UISwitch!
@IBOutlet weak var applePayShippingContactNameSwitch: UISwitch!
@IBOutlet weak var applePayShippingContactEmailSwitch: UISwitch!
@IBOutlet weak var applePayShippingContactPhoneSwitch: UISwitch!
@IBOutlet weak var applePayShippingContactPostalAddressSwitch: UISwitch!

// MARK: Order Inputs

Expand Down Expand Up @@ -159,14 +165,12 @@ class MerchantSessionAndSettingsViewController: UIViewController {
var payAfterVaultSuccess: Bool = false

var applePayCaptureBillingAddress = false
var applePayBillingAdditionalContactFields: [PrimerApplePayOptions.RequiredContactField]? = []
var applePayCaptureShippingDetails = false
var applePayRequireShippingMethod = false
var applePayShippingAdditionalContactFields: [PrimerApplePayOptions.RequiredContactField]? = []
var applePayCheckProvidedNetworks = false

//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]

func setAccessibilityIds() {
self.view.accessibilityIdentifier = "Background View"
self.testingModeSegmentedControl.accessibilityIdentifier = "Testing Mode Segmented Control"
Expand Down Expand Up @@ -417,66 +421,139 @@ class MerchantSessionAndSettingsViewController: UIViewController {
}

@IBAction func applePayCaptureBillingAddressSwitchValueChanged(_ sender: UISwitch) {
applePayBillingControlStackView.isHidden = !sender.isOn
applePayCaptureBillingAddress = sender.isOn
}


@IBAction func applePayBillingContactNameSwitchChanged(_ sender: UISwitch) {
if sender.isOn {
var fields = applePayBillingAdditionalContactFields ?? []
if !fields.contains(.name) {
fields.append(.name)
}
applePayBillingAdditionalContactFields = fields
} else {
applePayBillingAdditionalContactFields?.removeAll(where: { $0 == .name })
if applePayBillingAdditionalContactFields?.isEmpty == true {
applePayBillingAdditionalContactFields = nil
}
}
}


@IBAction func applePayBillingContactEmailField(_ sender: UISwitch) {
if sender.isOn {
var fields = applePayBillingAdditionalContactFields ?? []
if !fields.contains(.emailAddress) {
fields.append(.emailAddress)
}
applePayBillingAdditionalContactFields = fields
} else {
applePayBillingAdditionalContactFields?.removeAll(where: { $0 == .emailAddress })
if applePayBillingAdditionalContactFields?.isEmpty == true {
applePayBillingAdditionalContactFields = nil
}
}
}

@IBAction func applePayBillingContactPhoneSwitchChanged(_ sender: UISwitch) {
if sender.isOn {
var fields = applePayBillingAdditionalContactFields ?? []
if !fields.contains(.phoneNumber) {
fields.append(.phoneNumber)
}
applePayBillingAdditionalContactFields = fields
} else {
applePayBillingAdditionalContactFields?.removeAll(where: { $0 == .phoneNumber })
if applePayBillingAdditionalContactFields?.isEmpty == true {
applePayBillingAdditionalContactFields = nil
}
}
}

@IBAction func applePayBillingContactPostalAddressSwitchChanged(_ sender: UISwitch) {
if sender.isOn {
var fields = applePayBillingAdditionalContactFields ?? []
if !fields.contains(.postalAddress) {
fields.append(.postalAddress)
}
applePayBillingAdditionalContactFields = fields
} else {
applePayBillingAdditionalContactFields?.removeAll(where: { $0 == .postalAddress })
if applePayBillingAdditionalContactFields?.isEmpty == true {
applePayBillingAdditionalContactFields = nil
}
}
}

@IBAction func applePayCaptureShippingDetailsSwitchChanged(_ sender: UISwitch) {
applePayShippingControlStackView.isHidden = !sender.isOn
applePayCaptureShippingDetails = sender.isOn
}

@IBAction func captureShippingAddressSwitchChanged(_ sender: UISwitch) {
applePayCaptureShippingAddress = sender.isOn
}

@IBAction func applePayRequireShippingMethodSwitchChanged(_ sender: UISwitch) {
applePayRequireShippingMethod = sender.isOn
}

@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 = applePayShippingAdditionalContactFields ?? []
if !fields.contains(.name) {
fields.append(.name)
}
applePayShippingAdditionalContactFields = fields
} else {
applePayShippingAdditionalContactFields?.removeAll(where: { $0 == .name })
if applePayShippingAdditionalContactFields?.isEmpty == true {
applePayShippingAdditionalContactFields = nil
}
}
}


@IBAction func applePayShippingContactEmailField(_ sender: UISwitch) {
// if sender.isOn {
// var fields = applePayAdditionalContactFields ?? []
// if !fields.contains(.emailAddress) {
// fields.append(.emailAddress)
// }
// applePayAdditionalContactFields = fields
// } else {
// applePayAdditionalContactFields?.removeAll(where: { $0 == .emailAddress })
// if applePayAdditionalContactFields?.isEmpty == true {
// applePayAdditionalContactFields = nil
// }
// }
if sender.isOn {
var fields = applePayShippingAdditionalContactFields ?? []
if !fields.contains(.emailAddress) {
fields.append(.emailAddress)
}
applePayShippingAdditionalContactFields = fields
} else {
applePayShippingAdditionalContactFields?.removeAll(where: { $0 == .emailAddress })
if applePayShippingAdditionalContactFields?.isEmpty == true {
applePayShippingAdditionalContactFields = nil
}
}
}

@IBAction func applePayShippingContactPhoneSwitchChanged(_ sender: UISwitch) {
// if sender.isOn {
// var fields = applePayAdditionalContactFields ?? []
// if !fields.contains(.phoneNumber) {
// fields.append(.phoneNumber)
// }
// applePayAdditionalContactFields = fields
// } else {
// applePayAdditionalContactFields?.removeAll(where: { $0 == .phoneNumber })
// if applePayAdditionalContactFields?.isEmpty == true {
// applePayAdditionalContactFields = nil
// }
// }
if sender.isOn {
var fields = applePayShippingAdditionalContactFields ?? []
if !fields.contains(.phoneNumber) {
fields.append(.phoneNumber)
}
applePayShippingAdditionalContactFields = fields
} else {
applePayShippingAdditionalContactFields?.removeAll(where: { $0 == .phoneNumber })
if applePayShippingAdditionalContactFields?.isEmpty == true {
applePayShippingAdditionalContactFields = nil
}
}
}

@IBAction func applePayShippingContactPostalAddressSwitchChanged(_ sender: UISwitch) {
if sender.isOn {
var fields = applePayShippingAdditionalContactFields ?? []
if !fields.contains(.postalAddress) {
fields.append(.postalAddress)
}
applePayShippingAdditionalContactFields = fields
} else {
applePayShippingAdditionalContactFields?.removeAll(where: { $0 == .postalAddress })
if applePayShippingAdditionalContactFields?.isEmpty == true {
applePayShippingAdditionalContactFields = nil
}
}
}

@IBAction func applePayCheckProvidedNetworksSwitchValueChanged(_ sender: UISwitch) {
Expand Down Expand Up @@ -646,11 +723,12 @@ class MerchantSessionAndSettingsViewController: UIViewController {

let mandateData = PrimerStripeOptions.MandateData.templateMandate(merchantName: "Primer Inc.")

// For Express Checkout Beta
// let shippingOptions = applePayCaptureShippingDetails ?
// PrimerApplePayOptions.ShippingOptions(isCaptureShippingAddressEnabled: true,
// additionalShippingContactFields: [.name, .emailAddress, .phoneNumber],
// requireShippingMethod: true) : nil
let shippingOptions = applePayCaptureShippingDetails ?
PrimerApplePayOptions.ShippingOptions(shippingContactFields: applePayShippingAdditionalContactFields,
requireShippingMethod: applePayRequireShippingMethod) : nil

let billingOptions = applePayCaptureBillingAddress ?
PrimerApplePayOptions.BillingOptions(requiredBillingContactFields: applePayBillingAdditionalContactFields) : nil

let stripePublishableKey = SecretsManager.shared.value(forKey: .stripePublishableKey)

Expand All @@ -663,7 +741,9 @@ class MerchantSessionAndSettingsViewController: UIViewController {
merchantName: merchantNameTextField.text ?? "Primer Merchant",
isCaptureBillingAddressEnabled: applePayCaptureBillingAddress,
showApplePayForUnsupportedDevice: false,
checkProvidedNetworks: applePayCheckProvidedNetworks),
checkProvidedNetworks: applePayCheckProvidedNetworks,
shippingOptions: shippingOptions,
billingOptions: billingOptions),
stripeOptions: stripePublishableKey == nil ? nil : PrimerStripeOptions(publishableKey: stripePublishableKey!, mandateData: mandateData)),
uiOptions: uiOptions,
debugOptions: PrimerDebugOptions(is3DSSanityCheckEnabled: false)
Expand All @@ -687,10 +767,12 @@ class MerchantSessionAndSettingsViewController: UIViewController {
@IBAction func primerHeadlessButtonTapped(_ sender: Any) {
customDefinedApiKey = (apiKeyTextField.text ?? "").isEmpty ? nil : apiKeyTextField.text

// let shippingOptions = applePayCaptureShippingDetails ?
// PrimerApplePayOptions.ShippingOptions(isCaptureShippingAddressEnabled: true,
// additionalShippingContactFields: [.name, .emailAddress, .phoneNumber],
// requireShippingMethod: true) : nil
let shippingOptions = applePayCaptureShippingDetails ?
PrimerApplePayOptions.ShippingOptions(shippingContactFields: applePayShippingAdditionalContactFields,
requireShippingMethod: applePayRequireShippingMethod) : nil

let billingOptions = applePayCaptureBillingAddress ?
PrimerApplePayOptions.BillingOptions(requiredBillingContactFields: applePayBillingAdditionalContactFields) : nil

let stripePublishableKey = SecretsManager.shared.value(forKey: .stripePublishableKey)

Expand All @@ -703,7 +785,9 @@ class MerchantSessionAndSettingsViewController: UIViewController {
merchantName: merchantNameTextField.text ?? "Primer Merchant",
isCaptureBillingAddressEnabled: applePayCaptureBillingAddress,
showApplePayForUnsupportedDevice: false,
checkProvidedNetworks: applePayCheckProvidedNetworks),
checkProvidedNetworks: applePayCheckProvidedNetworks,
shippingOptions: shippingOptions,
billingOptions: billingOptions),
stripeOptions: stripePublishableKey == nil ? nil : PrimerStripeOptions(publishableKey: stripePublishableKey!)),
uiOptions: nil,
debugOptions: PrimerDebugOptions(is3DSSanityCheckEnabled: false)
Expand Down
47 changes: 28 additions & 19 deletions Sources/PrimerSDK/Classes/Data Models/PrimerSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ public class PrimerApplePayOptions: Codable {
let merchantIdentifier: String
@available(*, deprecated, message: "Use Client Session API to provide merchant name value: https://primer.io/docs/payment-methods/apple-pay/direct-integration#prepare-the-client-session")
let merchantName: String?
@available(*, deprecated, message: "Use BillingOptions to configure required billing fields.")
let isCaptureBillingAddressEnabled: Bool
/// If in some cases you dont want to present ApplePay option if the device is not supporting it set this to `false`.
/// Default value is `true`.
Expand All @@ -151,6 +152,7 @@ public class PrimerApplePayOptions: Codable {
/// we introduced this flag to continue supporting the old behaviour. Default value is `true`.
let checkProvidedNetworks: Bool
let shippingOptions: ShippingOptions?
let billingOptions: BillingOptions?

public init(merchantIdentifier: String,
merchantName: String?,
Expand All @@ -160,43 +162,50 @@ public class PrimerApplePayOptions: Codable {
self.merchantIdentifier = merchantIdentifier
self.merchantName = merchantName
self.isCaptureBillingAddressEnabled = isCaptureBillingAddressEnabled
self.shippingOptions = nil
self.showApplePayForUnsupportedDevice = showApplePayForUnsupportedDevice
self.checkProvidedNetworks = checkProvidedNetworks
self.shippingOptions = nil
self.billingOptions = nil
}

private init(merchantIdentifier: String,
merchantName: String?,
isCaptureBillingAddressEnabled: Bool = false,
showApplePayForUnsupportedDevice: Bool = true,
checkProvidedNetworks: Bool = true,
shippingOptions: ShippingOptions? = nil) {
public init(merchantIdentifier: String,
merchantName: String?,
isCaptureBillingAddressEnabled: Bool = false,
showApplePayForUnsupportedDevice: Bool = true,
checkProvidedNetworks: Bool = true,
shippingOptions: ShippingOptions? = nil,
billingOptions: BillingOptions? = nil) {
self.merchantIdentifier = merchantIdentifier
self.merchantName = merchantName
self.isCaptureBillingAddressEnabled = isCaptureBillingAddressEnabled
self.shippingOptions = shippingOptions
self.showApplePayForUnsupportedDevice = showApplePayForUnsupportedDevice
self.checkProvidedNetworks = checkProvidedNetworks
self.shippingOptions = shippingOptions
self.billingOptions = billingOptions
}

internal struct ShippingOptions: Codable {
let isCaptureShippingAddressEnabled: Bool
let additionalShippingContactFields: [AdditionalShippingContactField]?
let requireShippingMethod: Bool
public struct ShippingOptions: Codable {
public let shippingContactFields: [RequiredContactField]?
public let requireShippingMethod: Bool

public init(isCaptureShippingAddressEnabled: Bool,
additionalShippingContactFields: [AdditionalShippingContactField]? = nil,
public init(shippingContactFields: [RequiredContactField]? = nil,
requireShippingMethod: Bool) {
self.isCaptureShippingAddressEnabled = isCaptureShippingAddressEnabled
self.additionalShippingContactFields = additionalShippingContactFields
self.shippingContactFields = shippingContactFields
self.requireShippingMethod = requireShippingMethod
}
}

// swiftlint:disable:next nesting
internal enum AdditionalShippingContactField: Codable {
case name, emailAddress, phoneNumber
public struct BillingOptions: Codable {
public let requiredBillingContactFields: [RequiredContactField]?

public init(requiredBillingContactFields: [RequiredContactField]? = nil) {
self.requiredBillingContactFields = requiredBillingContactFields
}
}

public enum RequiredContactField: Codable {
case name, emailAddress, phoneNumber, postalAddress
}
}

// MARK: Klarna
Expand Down
Loading

0 comments on commit 443e6c7

Please sign in to comment.