Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update BTPayPalRequest with App Switch Properties #1465

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
61 changes: 50 additions & 11 deletions Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,45 @@ 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 '.'
/// - intent: Optional: Payment intent. Defaults to `.authorize`. Only applies to PayPal Checkout.
/// and is limited to 7 digits before the decimal point.
/// - 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,
enablePayPalAppSwitch: enablePayPalAppSwitch
)
}

/// Initializes a PayPal Native Checkout request
/// - Parameters:
Expand All @@ -98,22 +132,31 @@ 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.
/// - enablePayPalAppSwitch: Optional: Used to determine if the customer will use the PayPal app switch flow. Defaults to `false`.
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,
enablePayPalAppSwitch: Bool = false
) {
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,
enablePayPalAppSwitch: enablePayPalAppSwitch
)
}

// MARK: Public Methods
Expand All @@ -137,10 +180,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