Skip to content

Commit

Permalink
Update BTPayPalRequest with App Switch Properties (#1465)
Browse files Browse the repository at this point in the history
* add initial commits for updating `BTPayPalRequest` with app switch properties

* add changelog.md entry

* address pr feedback

* cleanup

* cleanup doc string

* address pr feedback
  • Loading branch information
agedd authored Nov 22, 2024
1 parent 3d4a3d3 commit e9b5a7e
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 87 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
## unreleased
* BraintreePayPal
* Add `BTPayPalRequest.userPhoneNumber` optional property
* Add PayPal App Switch checkout Flow (BETA)
* Add `BTPayPalCheckoutRequest(userAuthenticationEmail:enablePayPalAppSwitch:amount:intent:userAction: offerPayLater:currencyCode:requestBillingAgreement:)`
* **Note:** This feature is currently in beta and may change or be removed in future releases.
* BraintreeVenmo
* Send `url` in `event_params` for App Switch events to PayPal's analytics service (FPTI)

Expand Down
57 changes: 46 additions & 11 deletions Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,44 @@ import BraintreeCore

/// Optional: If set to `true`, this enables the Checkout with Vault flow, where the customer will be prompted to consent to a billing agreement during checkout. Defaults to `false`.
public var requestBillingAgreement: Bool

/// Optional: User email to initiate a quicker authentication flow in cases where the user has a PayPal Account with the same email.
public var userAuthenticationEmail: String?

// MARK: - Initializer
// MARK: - Initializers

/// Initializes a PayPal Checkout request for the PayPal App Switch flow
/// - Parameters:
/// - userAuthenticationEmail: Required: User email to initiate a quicker authentication flow in cases where the user has a PayPal Account with the same email.
/// - enablePayPalAppSwitch: Required: Used to determine if the customer will use the PayPal app switch flow.
/// - amount: Required: Used for a one-time payment. Amount must be greater than or equal to zero, may optionally contain exactly 2 decimal places separated by '.' and is limited to 7 digits before the decimal point.
/// - intent: Optional: Payment intent. Defaults to `.authorize`. Only applies to PayPal Checkout.
/// - userAction: Optional: Changes the call-to-action in the PayPal Checkout flow. Defaults to `.none`.
/// - offerPayLater: Optional: Offers PayPal Pay Later if the customer qualifies. Defaults to `false`. Only available with PayPal Checkout.
/// - currencyCode: Optional: A three-character ISO-4217 ISO currency code to use for the transaction. Defaults to merchant currency code if not set.
/// See https://developer.paypal.com/docs/api/reference/currency-codes/ for a list of supported currency codes.
/// - requestBillingAgreement: Optional: If set to `true`, this enables the Checkout with Vault flow, where the customer will be prompted to consent to a billing agreement
/// during checkout. Defaults to `false`.
/// - Warning: This initializer should be used for merchants using the PayPal App Switch flow. This feature is currently in beta and may change or be removed in future releases.
/// - Note: The PayPal App Switch flow currently only supports the production environment.
public convenience init(
userAuthenticationEmail: String,
enablePayPalAppSwitch: Bool,
amount: String,
intent: BTPayPalRequestIntent = .authorize,
userAction: BTPayPalRequestUserAction = .none,
offerPayLater: Bool = false,
currencyCode: String? = nil,
requestBillingAgreement: Bool = false
) {
self.init(
amount: amount,
intent: intent,
userAction: userAction,
offerPayLater: offerPayLater,
currencyCode: currencyCode,
requestBillingAgreement: requestBillingAgreement,
userAuthenticationEmail: userAuthenticationEmail
)
super.enablePayPalAppSwitch = enablePayPalAppSwitch
}

/// Initializes a PayPal Native Checkout request
/// - Parameters:
Expand All @@ -98,22 +131,28 @@ import BraintreeCore
/// See https://developer.paypal.com/docs/api/reference/currency-codes/ for a list of supported currency codes.
/// - requestBillingAgreement: Optional: If set to `true`, this enables the Checkout with Vault flow, where the customer will be prompted to consent to a billing agreement
/// during checkout. Defaults to `false`.
/// - userAuthenticationEmail: Optional: User email to initiate a quicker authentication flow in cases where the user has a PayPal Account with the same email.
public init(
amount: String,
intent: BTPayPalRequestIntent = .authorize,
userAction: BTPayPalRequestUserAction = .none,
offerPayLater: Bool = false,
currencyCode: String? = nil,
requestBillingAgreement: Bool = false
requestBillingAgreement: Bool = false,
userAuthenticationEmail: String? = nil
) {
self.amount = amount
self.intent = intent
self.userAction = userAction
self.offerPayLater = offerPayLater
self.currencyCode = currencyCode
self.requestBillingAgreement = requestBillingAgreement

super.init(hermesPath: "v1/paypal_hermes/create_payment_resource", paymentType: .checkout)

super.init(
hermesPath: "v1/paypal_hermes/create_payment_resource",
paymentType: .checkout,
userAuthenticationEmail: userAuthenticationEmail
)
}

// MARK: Public Methods
Expand All @@ -137,10 +176,6 @@ import BraintreeCore
if currencyCode != nil {
checkoutParameters["currency_iso_code"] = currencyCode
}

if let userAuthenticationEmail, !userAuthenticationEmail.isEmpty {
checkoutParameters["payer_email"] = userAuthenticationEmail
}

if userAction != .none, var experienceProfile = baseParameters["experience_profile"] as? [String: Any] {
experienceProfile["user_action"] = userAction.stringValue
Expand Down
104 changes: 66 additions & 38 deletions Sources/BraintreePayPal/BTPayPalRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import BraintreeCore

/// Checkout
case checkout

/// Vault
case vault

Expand All @@ -24,24 +24,24 @@ import BraintreeCore

/// Use this option to specify the PayPal page to display when a user lands on the PayPal site to complete the payment.
@objc public enum BTPayPalRequestLandingPageType: Int {

/// Default
case none // Obj-C enums cannot be nil; this default option is used to make `landingPageType` optional for merchants

/// Login
case login

/// Billing
case billing

var stringValue: String? {
switch self {
case .login:
return "login"

case .billing:
return "billing"

default:
return nil
}
Expand All @@ -51,62 +51,71 @@ import BraintreeCore
/// Base options for PayPal Checkout and PayPal Vault flows.
/// - Note: Do not instantiate this class directly. Instead, use BTPayPalCheckoutRequest or BTPayPalVaultRequest.
@objcMembers open class BTPayPalRequest: NSObject {

// MARK: - Public Properties

/// Defaults to false. When set to true, the shipping address selector will be displayed.
public var isShippingAddressRequired: Bool

/// Defaults to false. Set to true to enable user editing of the shipping address.
/// - Note: Only applies when `shippingAddressOverride` is set.
public var isShippingAddressEditable: Bool

/// Optional: A locale code to use for the transaction.
public var localeCode: BTPayPalLocaleCode

/// Optional: A valid shipping address to be displayed in the transaction flow. An error will occur if this address is not valid.
public var shippingAddressOverride: BTPostalAddress?

/// Optional: Landing page type. Defaults to `.none`.
/// - Note: Setting the BTPayPalRequest's landingPageType changes the PayPal page to display when a user lands on the PayPal site to complete the payment.
/// `.login` specifies a PayPal account login page is used.
/// `.billing` specifies a non-PayPal account landing page is used.
public var landingPageType: BTPayPalRequestLandingPageType

/// Optional: The merchant name displayed inside of the PayPal flow; defaults to the company name on your Braintree account
public var displayName: String?

/// Optional: A non-default merchant account to use for tokenization.
public var merchantAccountID: String?

/// Optional: The line items for this transaction. It can include up to 249 line items.
public var lineItems: [BTPayPalLineItem]?

/// Optional: Display a custom description to the user for a billing agreement. For Checkout with Vault flows, you must also set
/// `requestBillingAgreement` to `true` on your `BTPayPalCheckoutRequest`.
public var billingAgreementDescription: String?

/// Optional: A risk correlation ID created with Set Transaction Context on your server.
public var riskCorrelationID: String?

/// :nodoc: Exposed publicly for use by PayPal Native Checkout module. This property is not covered by semantic versioning.
@_documentation(visibility: private)
public var hermesPath: String

/// :nodoc: Exposed publicly for use by PayPal Native Checkout module. This property is not covered by semantic versioning.
@_documentation(visibility: private)
public var paymentType: BTPayPalPaymentType

/// Optional: A user's phone number to initiate a quicker authentication flow in the scenario where the user has a PayPal account
/// identified with the same phone number.
public var userPhoneNumber: BTPayPalPhoneNumber?


/// Optional: User email to initiate a quicker authentication flow in cases where the user has a PayPal Account with the same email.
public var userAuthenticationEmail: String?

// MARK: - Internal Properties

/// Optional: Used to determine if the customer will use the PayPal app switch flow. Defaults to `false`.
/// - Warning: This property is currently in beta and may change or be removed in future releases.
var enablePayPalAppSwitch: Bool

// MARK: - Static Properties

static let callbackURLHostAndPath: String = "onetouch/v1/"

// MARK: - Initializer

init(
hermesPath: String,
paymentType: BTPayPalPaymentType,
Expand All @@ -120,7 +129,9 @@ import BraintreeCore
lineItems: [BTPayPalLineItem]? = nil,
billingAgreementDescription: String? = nil,
riskCorrelationId: String? = nil,
userPhoneNumber: BTPayPalPhoneNumber? = nil
userPhoneNumber: BTPayPalPhoneNumber? = nil,
userAuthenticationEmail: String? = nil,
enablePayPalAppSwitch: Bool = false
) {
self.hermesPath = hermesPath
self.paymentType = paymentType
Expand All @@ -135,10 +146,12 @@ import BraintreeCore
self.billingAgreementDescription = billingAgreementDescription
self.riskCorrelationID = riskCorrelationId
self.userPhoneNumber = userPhoneNumber
self.userAuthenticationEmail = userAuthenticationEmail
self.enablePayPalAppSwitch = enablePayPalAppSwitch
}

// MARK: Public Methods

/// :nodoc: Exposed publicly for use by PayPal Native Checkout module. This method is not covered by semantic versioning.
@_documentation(visibility: private)
public func parameters(
Expand All @@ -147,43 +160,58 @@ import BraintreeCore
isPayPalAppInstalled: Bool = false
) -> [String: Any] {
var experienceProfile: [String: Any] = [:]

experienceProfile["no_shipping"] = !isShippingAddressRequired
experienceProfile["brand_name"] = displayName != nil ? displayName : configuration.json?["paypal"]["displayName"].asString()

if landingPageType.stringValue != nil {
experienceProfile["landing_page_type"] = landingPageType.stringValue
}

if localeCode.stringValue != nil {
experienceProfile["locale_code"] = localeCode.stringValue
}

experienceProfile["address_override"] = shippingAddressOverride != nil ? !isShippingAddressEditable : false

var parameters: [String: Any] = [:]

if merchantAccountID != nil {
parameters["merchant_account_id"] = merchantAccountID
}

if riskCorrelationID != nil {
parameters["correlation_id"] = riskCorrelationID
}

if let lineItems, !lineItems.isEmpty {
let lineItemsArray = lineItems.compactMap { $0.requestParameters() }
parameters["line_items"] = lineItemsArray
}

if let userPhoneNumberDict = try? userPhoneNumber?.toDictionary() {
parameters["phone_number"] = userPhoneNumberDict
if let userPhoneNumberDictionary = try? userPhoneNumber?.toDictionary() {
parameters["phone_number"] = userPhoneNumberDictionary
}


if let userAuthenticationEmail, !userAuthenticationEmail.isEmpty {
parameters["payer_email"] = userAuthenticationEmail
}

parameters["return_url"] = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)success"
parameters["cancel_url"] = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)cancel"
parameters["experience_profile"] = experienceProfile


if let universalLink, enablePayPalAppSwitch, isPayPalAppInstalled {
let appSwitchParameters: [String: Any] = [
"launch_paypal_app": enablePayPalAppSwitch,
"os_version": UIDevice.current.systemVersion,
"os_type": UIDevice.current.systemName,
"merchant_app_return_url": universalLink.absoluteString
]

return parameters.merging(appSwitchParameters) { $1 }
}

return parameters
}
}
21 changes: 16 additions & 5 deletions Sources/BraintreePayPal/BTPayPalVaultBaseRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,25 @@ import BraintreeCore
public var offerCredit: Bool

// MARK: - Initializer

/// Initializes a PayPal Native Vault request
/// - Parameters:
/// - offerCredit: Optional: Offers PayPal Credit if the customer qualifies. Defaults to `false`.
public init(offerCredit: Bool = false) {
/// - userAuthenticationEmail: Optional: User email to initiate a quicker authentication flow in cases where the user has a PayPal Account with the same email.
/// - enablePayPalAppSwitch: Optional: Used to determine if the customer will use the PayPal app switch flow. Defaults to `false`.
public init(
offerCredit: Bool = false,
userAuthenticationEmail: String? = nil,
enablePayPalAppSwitch: Bool = false
) {
self.offerCredit = offerCredit

super.init(hermesPath: "v1/paypal_hermes/setup_billing_agreement", paymentType: .vault)

super.init(
hermesPath: "v1/paypal_hermes/setup_billing_agreement",
paymentType: .vault,
userAuthenticationEmail: userAuthenticationEmail,
enablePayPalAppSwitch: enablePayPalAppSwitch
)
}

// MARK: Public Methods
Expand All @@ -32,7 +43,7 @@ import BraintreeCore
universalLink: URL? = nil,
isPayPalAppInstalled: Bool = false
) -> [String: Any] {
let baseParameters = super.parameters(with: configuration)
let baseParameters = super.parameters(with: configuration, universalLink: universalLink, isPayPalAppInstalled: isPayPalAppInstalled)
var vaultParameters: [String: Any] = ["offer_paypal_credit": offerCredit]

if let billingAgreementDescription {
Expand Down
Loading

0 comments on commit e9b5a7e

Please sign in to comment.