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

[V7] Add Encodable protocol for PayPal Vault #1492

Open
wants to merge 27 commits into
base: paypal-checkout-encodable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8006e18
Add Encodable protocol to BTPostalAddress class
richherrera Jan 3, 2025
d1c1cd1
Add Encodable protocol to BTPayPalBillingCycle struct
richherrera Jan 3, 2025
14390fd
Add Encodable protocol to BTPayPalBillingPricing struct
richherrera Jan 3, 2025
06fd294
Add Encodable protocol to BTPayPalRecurringBillingDetails struct
richherrera Jan 3, 2025
6f0daad
Add Encodable protocol to BTPayPalRecurringBillingPlanType enum
richherrera Jan 3, 2025
428b0a4
Add PayPalVaultPOSTBody file
richherrera Jan 3, 2025
2b072da
Add encodable properties
richherrera Jan 3, 2025
c7e226e
Merge branch 'paypal-checkout-encodable' into paypal-vault-encodable
richherrera Jan 6, 2025
9543469
Use encodable for BTLineItems
richherrera Jan 6, 2025
27c0a44
Add error to catch encodable exception
richherrera Jan 6, 2025
4a6e7d8
Use encoded objects
richherrera Jan 6, 2025
42af3f7
Use Configuration.isPayPalEnabled instead of json format
richherrera Jan 6, 2025
2200e5c
Add PayPalExperienceProfile as a shared object to be use for Checkout…
richherrera Jan 6, 2025
50c23ac
Merge branch 'paypal-checkout-encodable' into paypal-vault-encodable
richherrera Jan 9, 2025
8e12bf8
Address PR comments
richherrera Jan 9, 2025
8b18aeb
Remove parameters method
richherrera Jan 9, 2025
b903ba2
Remove BTPayPalRequest class
richherrera Jan 9, 2025
44d61f4
Add PayPalConstanst enum
richherrera Jan 9, 2025
7f0a207
Remove unnecessary error
richherrera Jan 9, 2025
39ca19f
Move encoded method
richherrera Jan 9, 2025
169ff3a
Update UTs
richherrera Jan 9, 2025
d6284ec
Move BTPayPalRequest UTs to BTPayPalCheckout and BTPayPalVault requests
richherrera Jan 10, 2025
7601a15
Delete BTPayPalRequest UTs
richherrera Jan 10, 2025
5b86155
Merge branch 'paypal-checkout-encodable' into paypal-vault-encodable
richherrera Jan 13, 2025
f90356b
Rename PayPalRequest protocol to BTPayPalRequest
richherrera Jan 13, 2025
df8eba1
Remove unnecessary parameters methods
richherrera Jan 13, 2025
3fa9a0d
Remove unnecessary parameters methods
richherrera Jan 13, 2025
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
12 changes: 8 additions & 4 deletions Braintree.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
427F32E025D1D62D00435294 /* BTPayPalClient_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427F32DF25D1D62D00435294 /* BTPayPalClient_Tests.swift */; };
428F48E42624C9B700EC8DB4 /* BTVenmoAppSwitchRedirectURL_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 428F48E32624C9B700EC8DB4 /* BTVenmoAppSwitchRedirectURL_Tests.swift */; };
428F976626727333001042E1 /* BTMockOpenURLContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 428F976526727333001042E1 /* BTMockOpenURLContext.m */; };
42FC218B25CDE0290047C49A /* BTPayPalRequest_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42FC218A25CDE0290047C49A /* BTPayPalRequest_Tests.swift */; };
42FC237125CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42FC237025CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift */; };
45227FC52C330FDE00A15018 /* MockURLSessionTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45227FC32C330FDE00A15018 /* MockURLSessionTask.swift */; };
45227FC72C33104100A15018 /* MockBTHTTPNetworkTiming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45227FC62C33104100A15018 /* MockBTHTTPNetworkTiming.swift */; };
Expand All @@ -45,6 +44,8 @@
4585707A2C34B1E1009CEF7A /* MockClientAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458570792C34B1E1009CEF7A /* MockClientAuthorization.swift */; };
4585707C2C34B7B5009CEF7A /* MockConfigurationLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4585707B2C34B7B5009CEF7A /* MockConfigurationLoader.swift */; };
45E8CE4C2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE4B2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift */; };
45E8CE4E2D28611700D7A2DC /* PayPalVaultPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE4D2D28611700D7A2DC /* PayPalVaultPOSTBody.swift */; };
45E8CE502D2C773600D7A2DC /* PayPalExperienceProfilePOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE4F2D2C772000D7A2DC /* PayPalExperienceProfilePOSTBody.swift */; };
45E8CE522D2C920000D7A2DC /* LocalPaymentAccountsPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E8CE512D2C91F000D7A2DC /* LocalPaymentAccountsPOSTBody.swift */; };
45EFC3972C2DBF32005E7F5B /* ConfigurationLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45EFC3962C2DBF32005E7F5B /* ConfigurationLoader.swift */; };
460C0C220F594AE8EE205E57 /* Pods_Tests_BraintreeCoreTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9239C9FE850C3587DE61A3A2 /* Pods_Tests_BraintreeCoreTests.framework */; };
Expand Down Expand Up @@ -717,7 +718,6 @@
42F2FF2E2333D2B20083CA10 /* BTAuthenticationInsight_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAuthenticationInsight_Tests.swift; sourceTree = "<group>"; };
42F75E5824D46DA2007DC5E7 /* BTThreeDSecureLookup_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureLookup_Tests.swift; sourceTree = "<group>"; };
42F75E5A24D48138007DC5E7 /* BTThreeDSecureClient_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureClient_Tests.swift; sourceTree = "<group>"; };
42FC218A25CDE0290047C49A /* BTPayPalRequest_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalRequest_Tests.swift; sourceTree = "<group>"; };
42FC237025CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPayPalCheckoutRequest_Tests.swift; sourceTree = "<group>"; };
45227FC32C330FDE00A15018 /* MockURLSessionTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSessionTask.swift; sourceTree = "<group>"; };
45227FC62C33104100A15018 /* MockBTHTTPNetworkTiming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBTHTTPNetworkTiming.swift; sourceTree = "<group>"; };
Expand All @@ -728,6 +728,8 @@
458570792C34B1E1009CEF7A /* MockClientAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockClientAuthorization.swift; sourceTree = "<group>"; };
4585707B2C34B7B5009CEF7A /* MockConfigurationLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockConfigurationLoader.swift; sourceTree = "<group>"; };
45E8CE4B2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalCheckoutPOSTBody.swift; sourceTree = "<group>"; };
45E8CE4D2D28611700D7A2DC /* PayPalVaultPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalVaultPOSTBody.swift; sourceTree = "<group>"; };
45E8CE4F2D2C772000D7A2DC /* PayPalExperienceProfilePOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalExperienceProfilePOSTBody.swift; sourceTree = "<group>"; };
45E8CE512D2C91F000D7A2DC /* LocalPaymentAccountsPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalPaymentAccountsPOSTBody.swift; sourceTree = "<group>"; };
45EFC3962C2DBF32005E7F5B /* ConfigurationLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationLoader.swift; sourceTree = "<group>"; };
463DED22C0F426A474E6D7E2 /* Pods-Tests-BraintreeCoreTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-BraintreeCoreTests.release.xcconfig"; path = "Target Support Files/Pods-Tests-BraintreeCoreTests/Pods-Tests-BraintreeCoreTests.release.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1323,6 +1325,8 @@
isa = PBXGroup;
children = (
45E8CE4B2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift */,
45E8CE4D2D28611700D7A2DC /* PayPalVaultPOSTBody.swift */,
45E8CE4F2D2C772000D7A2DC /* PayPalExperienceProfilePOSTBody.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -1916,7 +1920,6 @@
42FC237025CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift */,
427F32DF25D1D62D00435294 /* BTPayPalClient_Tests.swift */,
BECB10C52B5999EE008D398E /* BTPayPalLineItem_Tests.swift */,
42FC218A25CDE0290047C49A /* BTPayPalRequest_Tests.swift */,
BEBA590E2BB1B5B9005FA8A2 /* BTPayPalReturnURL_Tests.swift */,
427F328F25D1A7B900435294 /* BTPayPalVaultRequest_Tests.swift */,
A9E5C1E424FD665D00EE691F /* Info.plist */,
Expand Down Expand Up @@ -3117,10 +3120,12 @@
BEF5D2E6294A18B300FFD56D /* BTPayPalLineItem.swift in Sources */,
BE349113294B798300D2CF68 /* BTPayPalRequest.swift in Sources */,
BE549F112BF5445F00B6F441 /* BTPayPalReturnURL.swift in Sources */,
45E8CE502D2C773600D7A2DC /* PayPalExperienceProfilePOSTBody.swift in Sources */,
57544F5C295254A500DEB7B0 /* BTJSON+PayPal.swift in Sources */,
3B7A261129C0CAA40087059D /* BTPayPalAnalytics.swift in Sources */,
BE8E5CEF294B6937001BF017 /* BTPayPalCheckoutRequest.swift in Sources */,
807D22F02C29A93A009FFEA4 /* BTPayPalBillingCycle.swift in Sources */,
45E8CE4E2D28611700D7A2DC /* PayPalVaultPOSTBody.swift in Sources */,
5754481E294A2A1D00DEB7B0 /* BTPayPalCreditFinancingAmount.swift in Sources */,
57D9436E2968A8080079EAB1 /* BTPayPalLocaleCode.swift in Sources */,
45E8CE4C2D1F29BA00D7A2DC /* PayPalCheckoutPOSTBody.swift in Sources */,
Expand Down Expand Up @@ -3433,7 +3438,6 @@
buildActionMask = 2147483647;
files = (
427F32E025D1D62D00435294 /* BTPayPalClient_Tests.swift in Sources */,
42FC218B25CDE0290047C49A /* BTPayPalRequest_Tests.swift in Sources */,
42FC237125CE0E110047C49A /* BTPayPalCheckoutRequest_Tests.swift in Sources */,
BEDEAF112AC1D049004EA970 /* BTPayPalAccountNonce_Tests.swift in Sources */,
427F329025D1A7B900435294 /* BTPayPalVaultRequest_Tests.swift in Sources */,
Expand Down
12 changes: 11 additions & 1 deletion Sources/BraintreeCore/BTPostalAddress.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// Generic postal address
@objcMembers public class BTPostalAddress: NSObject {
@objcMembers public class BTPostalAddress: NSObject, Encodable {

// Property names follow the `Braintree_Address` convention as documented at:
// https://developer.paypal.com/braintree/docs/reference/request/address/create
Expand All @@ -27,4 +27,14 @@ import Foundation

/// Either a two-letter state code (for the US), or an ISO-3166-2 country subdivision code of up to three letters.
public var region: String?

enum CodingKeys: String, CodingKey {
case countryCodeAlpha2 = "country_code"
case extendedAddress = "line2"
case locality = "city"
case postalCode = "postal_code"
case region = "state"
case recipientName = "recipient_name"
case streetAddress = "line1"
}
}
96 changes: 9 additions & 87 deletions Sources/BraintreePayPal/BTPayPalCheckoutRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import BraintreeCore
}

/// Options for the PayPal Checkout flow.
@objcMembers public class BTPayPalCheckoutRequest: NSObject, PayPalRequest {
@objcMembers public class BTPayPalCheckoutRequest: NSObject, BTPayPalRequest {

// MARK: - Internal Properties

Expand Down Expand Up @@ -149,92 +149,14 @@ import BraintreeCore
self.userAuthenticationEmail = userAuthenticationEmail
self.userPhoneNumber = userPhoneNumber
}

// MARK: Internal Methods

// swiftlint:disable cyclomatic_complexity
func parameters(
with configuration: BTConfiguration,
universalLink: URL? = nil,
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 let landingPageType = landingPageType?.stringValue {
experienceProfile["landing_page_type"] = landingPageType
}

if let localeCode = localeCode?.stringValue {
experienceProfile["locale_code"] = localeCode
}

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

var baseParameters: [String: Any] = [:]

if let merchantAccountID {
baseParameters["merchant_account_id"] = merchantAccountID
}

if let riskCorrelationID {
baseParameters["correlation_id"] = riskCorrelationID
}

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

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

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

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

var checkoutParameters: [String: Any] = [
"intent": intent.stringValue,
"amount": amount,
"offer_pay_later": offerPayLater
]

let currencyCode = currencyCode != nil ? currencyCode : configuration.json?["paypal"]["currencyIsoCode"].asString()

if let currencyCode {
checkoutParameters["currency_iso_code"] = currencyCode
}

if userAction != .none, var experienceProfile = baseParameters["experience_profile"] as? [String: Any] {
experienceProfile["user_action"] = userAction.stringValue
baseParameters["experience_profile"] = experienceProfile
}

if requestBillingAgreement {
checkoutParameters["request_billing_agreement"] = requestBillingAgreement

if billingAgreementDescription != nil {
checkoutParameters["billing_agreement_details"] = ["description": billingAgreementDescription]
}
}

if let shippingAddressOverride {
checkoutParameters["line1"] = shippingAddressOverride.streetAddress
checkoutParameters["line2"] = shippingAddressOverride.extendedAddress
checkoutParameters["city"] = shippingAddressOverride.locality
checkoutParameters["state"] = shippingAddressOverride.region
checkoutParameters["postal_code"] = shippingAddressOverride.postalCode
checkoutParameters["country_code"] = shippingAddressOverride.countryCodeAlpha2
checkoutParameters["recipient_name"] = shippingAddressOverride.recipientName
}

return baseParameters.merging(checkoutParameters) { $1 }

func encodedPostBodyWith(
configuration: BTConfiguration,
isPayPalAppInstalled: Bool = false,
universalLink: URL? = nil
) -> Encodable {
PayPalCheckoutPOSTBody(payPalRequest: self, configuration: configuration)
}
}
21 changes: 12 additions & 9 deletions Sources/BraintreePayPal/BTPayPalClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import BraintreeDataCollector
var clientMetadataID: String?

/// Exposed for testing the intent associated with this request
var payPalRequest: PayPalRequest?
var payPalRequest: BTPayPalRequest?

/// Exposed for testing, the ASWebAuthenticationSession instance used for the PayPal flow
var webAuthenticationSession: BTWebAuthenticationSession
Expand Down Expand Up @@ -331,7 +331,7 @@ import BraintreeDataCollector
// MARK: - Private Methods

private func tokenize(
request: PayPalRequest,
request: BTPayPalRequest,
completion: @escaping (BTPayPalAccountNonce?, Error?) -> Void
) {
linkType = (request as? BTPayPalVaultRequest)?.enablePayPalAppSwitch == true ? .universal : .deeplink
Expand All @@ -343,26 +343,29 @@ import BraintreeDataCollector
return
}

guard let configuration, let json = configuration.json else {
guard let configuration else {
self.notifyFailure(with: BTPayPalError.fetchConfigurationFailed, completion: completion)
return
}

self.isConfigFromCache = configuration.isFromCache

guard json["paypalEnabled"].isTrue else {
guard configuration.isPayPalEnabled else {
jaxdesmarais marked this conversation as resolved.
Show resolved Hide resolved
self.notifyFailure(with: BTPayPalError.disabled, completion: completion)
return
}

self.payPalRequest = request

let parameters = request.encodedPostBodyWith(
configuration: configuration,
isPayPalAppInstalled: self.application.isPayPalAppInstalled(),
universalLink: self.universalLink
)

self.apiClient.post(
request.hermesPath,
parameters: request.parameters(
with: configuration,
universalLink: self.universalLink,
isPayPalAppInstalled: self.application.isPayPalAppInstalled()
)
parameters: parameters
) { body, _, error in
if let error = error as? NSError {
guard let jsonResponseBody = error.userInfo[BTCoreConstants.jsonResponseBodyKey] as? BTJSON else {
Expand Down
43 changes: 0 additions & 43 deletions Sources/BraintreePayPal/BTPayPalLineItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,47 +146,4 @@ import Foundation
case upcType = "upc_type"
case url
}

// MARK: - Internal Methods

/// Returns the line item in a dictionary.
/// - Returns: A dictionary with the line item information formatted for a request.
func requestParameters() -> [String: String] {
var requestParameters = [
"quantity": quantity,
"unit_amount": unitAmount,
"name": name,
"kind": kind == .debit ? "debit" : "credit"
]

if let unitTaxAmount, !unitTaxAmount.isEmpty {
requestParameters["unit_tax_amount"] = unitAmount
}

if let itemDescription, !itemDescription.isEmpty {
requestParameters["description"] = itemDescription
}

if let productCode, !productCode.isEmpty {
requestParameters["product_code"] = productCode
}

if let url, url != URL(string: "") {
requestParameters["url"] = url.absoluteString
}

if let imageURL, imageURL != URL(string: "") {
requestParameters["image_url"] = imageURL.absoluteString
}

if let upcCode, !upcCode.isEmpty {
requestParameters["upc_code"] = upcCode
}

if upcType.stringValue != nil {
requestParameters["upc_type"] = upcType.stringValue
}

return requestParameters
}
}
Loading