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

Create encodable for paypal accounts post #1518

Open
wants to merge 25 commits into
base: v7
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1c3aeac
Introduce Paypal account encodable
warmkesselj Feb 6, 2025
64ac4ad
update encodable object
warmkesselj Feb 11, 2025
176bc41
Update Encodable object
warmkesselj Feb 11, 2025
635f6cc
Add paypal browser to source. clean up
warmkesselj Feb 12, 2025
532fb10
Merge branch 'v7' of https://github.com/braintree/braintree_ios into …
warmkesselj Feb 12, 2025
9a68e22
Refactor
warmkesselj Feb 12, 2025
3844f89
Single line for guard
warmkesselj Feb 12, 2025
82b5a4c
update unit tests
warmkesselj Feb 12, 2025
d3dd64a
Update unit tests
warmkesselj Feb 14, 2025
940d9a8
PR comments
warmkesselj Feb 14, 2025
8ef4fe3
PR comments
warmkesselj Feb 14, 2025
f0839eb
Swiftlint fix
warmkesselj Feb 20, 2025
f7ebcdf
PR comments
warmkesselj Feb 25, 2025
81e2835
Updates for meta
warmkesselj Feb 25, 2025
b58452b
apiClient metadata included
warmkesselj Feb 25, 2025
31ed09a
PayPalBrowser is hardcoded
warmkesselj Feb 25, 2025
3599deb
Merge branch 'v7' of https://github.com/braintree/braintree_ios into …
warmkesselj Feb 26, 2025
742f229
Update project file
warmkesselj Feb 26, 2025
ec402b2
PR comments
warmkesselj Mar 3, 2025
c3bc6a1
Add new test and update error handling
warmkesselj Mar 3, 2025
c1e8c73
Update unit tests
warmkesselj Mar 4, 2025
eddb580
Update Sources/BraintreePayPal/BTPayPalClient.swift
warmkesselj Mar 4, 2025
cc489ac
Move uit test
warmkesselj Mar 4, 2025
4c9b7dd
Merge branch 'create-encodable-paypal-accounts-post' of https://githu…
warmkesselj Mar 4, 2025
8073b07
Update name
warmkesselj Mar 4, 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
4 changes: 4 additions & 0 deletions Braintree.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@
A9E80A9F24FEF40C00196BD3 /* BraintreeTestShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A903E1A624F9D34000C314E1 /* BraintreeTestShared.framework */; };
A9E80AA324FEF4D800196BD3 /* MockLocalPaymentRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E80AA224FEF4D800196BD3 /* MockLocalPaymentRequestDelegate.swift */; };
B8BA342E2D4811560030423C /* BTContactInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BA342D2D4811560030423C /* BTContactInformation.swift */; };
B8F483CF2D6F8A5F00214627 /* PayPalAccountPOSTEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F483CE2D6F8A5F00214627 /* PayPalAccountPOSTEncodable.swift */; };
B8F870152D48435D000FCE3C /* CreditCardPOSTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F870142D48435D000FCE3C /* CreditCardPOSTBody.swift */; };
B8F870162D48435D000FCE3C /* CreditCardGraphQLBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F870132D48435D000FCE3C /* CreditCardGraphQLBody.swift */; };
BC17F9B428D23C5C004B18CC /* BTGraphQLMultiErrorNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC17F9B328D23C5C004B18CC /* BTGraphQLMultiErrorNode.swift */; };
Expand Down Expand Up @@ -900,6 +901,7 @@
A9E5C22824FD6D0800EE691F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A9E80AA224FEF4D800196BD3 /* MockLocalPaymentRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLocalPaymentRequestDelegate.swift; sourceTree = "<group>"; };
B8BA342D2D4811560030423C /* BTContactInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTContactInformation.swift; sourceTree = "<group>"; };
B8F483CE2D6F8A5F00214627 /* PayPalAccountPOSTEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalAccountPOSTEncodable.swift; sourceTree = "<group>"; };
B8F870132D48435D000FCE3C /* CreditCardGraphQLBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardGraphQLBody.swift; sourceTree = "<group>"; };
B8F870142D48435D000FCE3C /* CreditCardPOSTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardPOSTBody.swift; sourceTree = "<group>"; };
BC17F9B328D23C5C004B18CC /* BTGraphQLMultiErrorNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTGraphQLMultiErrorNode.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1301,6 +1303,7 @@
2D941D391B59C76A0016EFB4 /* BraintreePayPal */ = {
isa = PBXGroup;
children = (
B8F483CE2D6F8A5F00214627 /* PayPalAccountPOSTEncodable.swift */,
57544821294A3B6900DEB7B0 /* BTConfiguration+PayPal.swift */,
B8BA342D2D4811560030423C /* BTContactInformation.swift */,
57544F5B295254A500DEB7B0 /* BTJSON+PayPal.swift */,
Expand Down Expand Up @@ -3148,6 +3151,7 @@
BE549F112BF5445F00B6F441 /* BTPayPalReturnURL.swift in Sources */,
45E8CE502D2C773600D7A2DC /* PayPalExperienceProfilePOSTBody.swift in Sources */,
57544F5C295254A500DEB7B0 /* BTJSON+PayPal.swift in Sources */,
B8F483CF2D6F8A5F00214627 /* PayPalAccountPOSTEncodable.swift in Sources */,
3B7A261129C0CAA40087059D /* BTPayPalAnalytics.swift in Sources */,
451508382D400C400071E385 /* BTPayPalRequestLandingPageType.swift in Sources */,
BE8E5CEF294B6937001BF017 /* BTPayPalCheckoutRequest.swift in Sources */,
Expand Down
29 changes: 29 additions & 0 deletions IntegrationTests/BraintreePayPal_IntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class BraintreePayPal_IntegrationTests: XCTestCase {
}

let payPalClient = BTPayPalClient(apiClient: apiClient)

payPalClient.payPalRequest = BTPayPalVaultRequest()

let tokenizationExpectation = expectation(description: "Tokenize one-time payment")
let returnURL = URL(string: oneTouchCoreAppSwitchSuccessURLFixture)

Expand All @@ -39,6 +42,8 @@ class BraintreePayPal_IntegrationTests: XCTestCase {
}

let payPalClient = BTPayPalClient(apiClient: apiClient)
payPalClient.payPalRequest = BTPayPalVaultRequest()

let tokenizationExpectation = expectation(description: "Tokenize one-time payment")
let returnURL = URL(string: oneTouchCoreAppSwitchSuccessURLFixture)

Expand All @@ -56,6 +61,26 @@ class BraintreePayPal_IntegrationTests: XCTestCase {
waitForExpectations(timeout: 5)
}

func testCheckoutFlow_withoutPayPalRequest_returnsError() {
guard let apiClient = BTAPIClient(authorization: BTIntegrationTestsConstants.sandboxTokenizationKey) else {
XCTFail("Failed to initialize BTAPIClient with sandbox tokenization key.")
return
}

let payPalClient = BTPayPalClient(apiClient: apiClient)

let tokenizationExpectation = expectation(description: "Tokenize one-time payment")
let returnURL = URL(string: oneTouchCoreAppSwitchSuccessURLFixture)

payPalClient.handleReturn(returnURL, paymentType: .checkout) { tokenizedPayPalAccount, error in
XCTAssertNotNil(error)
XCTAssertEqual(error?.localizedDescription, "The PayPal Request was missing or invalid.")
tokenizationExpectation.fulfill()
}

waitForExpectations(timeout: 5)
}

// MARK: - Vault Flow Tests

func testVaultFlow_withTokenizationKey_tokenizesPayPalAccount() {
Expand All @@ -65,6 +90,8 @@ class BraintreePayPal_IntegrationTests: XCTestCase {
}

let payPalClient = BTPayPalClient(apiClient: apiClient)
payPalClient.payPalRequest = BTPayPalVaultRequest()

let tokenizationExpectation = expectation(description: "Tokenize billing agreement payment")
let returnURL = URL(string: oneTouchCoreAppSwitchSuccessURLFixture)

Expand All @@ -89,6 +116,8 @@ class BraintreePayPal_IntegrationTests: XCTestCase {
}

let payPalClient = BTPayPalClient(apiClient: apiClient)
payPalClient.payPalRequest = BTPayPalVaultRequest()

let tokenizationExpectation = expectation(description: "Tokenize billing agreement payment")
let returnURL = URL(string: oneTouchCoreAppSwitchSuccessURLFixture)

Expand Down
50 changes: 12 additions & 38 deletions Sources/BraintreePayPal/BTPayPalClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -208,48 +208,22 @@ import BraintreeDataCollector
notifyCancel(completion: completion)
return
}

let clientDictionary: [String: String] = [
"platform": "iOS",
"product_name": "PayPal",
"paypal_sdk_version": "version"
]

let responseDictionary: [String: String] = ["webURL": url.absoluteString]

var account: [String: Any] = [
"client": clientDictionary,
"response": responseDictionary,
"response_type": "web"
]

if paymentType == .checkout {
account["options"] = ["validate": false]
if let request = payPalRequest as? BTPayPalCheckoutRequest {
account["intent"] = request.intent.stringValue
}
}

if let clientMetadataID {
account["correlation_id"] = clientMetadataID
}

var parameters: [String: Any] = ["paypal_account": account]

if let payPalRequest, let merchantAccountID = payPalRequest.merchantAccountID {
parameters["merchant_account_id"] = merchantAccountID
guard let payPalRequest else {
notifyFailure(with: BTPayPalError.missingPayPalRequest, completion: completion)
return
}

let metadata = apiClient.metadata
metadata.source = .payPalBrowser

parameters["_meta"] = [
"source": metadata.source.stringValue,
"integration": metadata.integration.stringValue,
"sessionId": metadata.sessionID
]
let encodableParams = PayPalAccountPOSTEncodable(
metadata: apiClient.metadata,
request: payPalRequest,
client: apiClient,
paymentType: paymentType,
url: url,
correlationID: clientMetadataID
)

apiClient.post("/v1/payment_methods/paypal_accounts", parameters: parameters) { body, _, error in
apiClient.post("/v1/payment_methods/paypal_accounts", parameters: encodableParams) { body, _, error in
if let error {
self.notifyFailure(with: error, completion: completion)
return
Expand Down
118 changes: 118 additions & 0 deletions Sources/BraintreePayPal/PayPalAccountPOSTEncodable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import Foundation

#if canImport(BraintreeCore)
import BraintreeCore
#endif

/// The POST body for `/v1/payment_methods/paypal_accounts`
struct PayPalAccountPOSTEncodable: Encodable {

let meta: Meta
let paypalAccount: PayPalAccount
let merchantAccountID: String?

init(
metadata: BTClientMetadata,
request: BTPayPalRequest,
client: BTAPIClient,
paymentType: BTPayPalPaymentType,
url: URL?,
correlationID: String?
) {
self.meta = Meta(
sessionID: metadata.sessionID,
integration: metadata.integration.stringValue,
source: BTClientMetadataSource.payPalBrowser.stringValue
)

self.paypalAccount = PayPalAccount(
request: request,
client: client,
paymentType: paymentType,
url: url,
correlationID: correlationID
)

self.merchantAccountID = request.merchantAccountID
}

enum CodingKeys: String, CodingKey {
case meta = "_meta"
case paypalAccount = "paypal_account"
case merchantAccountID = "merchant_account_id"
}
}

struct Meta: Encodable {

let sessionID: String
let integration: String
let source: String

enum CodingKeys: String, CodingKey {
case sessionID = "sessionId"
case integration
case source
}
}


struct PayPalAccount: Encodable {

let responseType: String
let intent: String?
let correlationID: String?
let options: Options?
let client: Client
let response: PayPalResponse

init(
request: BTPayPalRequest,
client: BTAPIClient,
paymentType: BTPayPalPaymentType,
url: URL?,
correlationID: String?,
responseType: String = "web"
) {
self.responseType = responseType
self.correlationID = correlationID

options = paymentType == .checkout ? Options(validate: false) : nil
intent = paymentType == .checkout ? (request as? BTPayPalCheckoutRequest)?.intent.stringValue : nil

self.client = Client()
self.response = PayPalResponse(webURL: url?.absoluteString ?? "")
}

enum CodingKeys: String, CodingKey {
case responseType = "response_type"
case intent
case correlationID = "correlation_id"
case options
case client
case response
}
}

struct Options: Encodable {

let validate: Bool
}

struct Client: Encodable {

let platform = "iOS"
let productName = "PayPal"
let paypalSdkVersion = "version"

enum CodingKeys: String, CodingKey {
case platform
case productName = "product_name"
case paypalSdkVersion = "paypal_sdk_version"
}
}

struct PayPalResponse: Encodable {

let webURL: String
}
32 changes: 31 additions & 1 deletion UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,10 @@ class BTPayPalClient_Tests: XCTestCase {

func testHandleBrowserSwitchReturn_whenBrowserSwitchSucceeds_tokenizesPayPalCheckout() {
let returnURL = URL(string: "bar://onetouch/v1/success?token=hermes_token")!

let payPalRequest = BTPayPalVaultRequest()
payPalClient.payPalRequest = payPalRequest

payPalClient.handleReturn(returnURL, paymentType: .checkout) { _, _ in }

XCTAssertEqual(mockAPIClient.lastPOSTPath, "/v1/payment_methods/paypal_accounts")
Expand Down Expand Up @@ -430,6 +434,8 @@ class BTPayPalClient_Tests: XCTestCase {
func testHandleBrowserSwitchReturn_whenBrowserSwitchSucceeds_intentShouldBeNilForVaultRequests() {
let payPalRequest = BTPayPalVaultRequest()
let returnURL = URL(string: "bar://onetouch/v1/success?ec-token=ec_token")!

payPalClient.payPalRequest = payPalRequest
payPalClient.handleReturn(returnURL, paymentType: payPalRequest.paymentType) { _, _ in }

XCTAssertEqual(mockAPIClient.lastPOSTPath, "/v1/payment_methods/paypal_accounts")
Expand All @@ -441,8 +447,12 @@ class BTPayPalClient_Tests: XCTestCase {

func testHandleBrowserSwitchReturn_whenBrowserSwitchSucceeds_merchantAccountIdIsSet() {
let merchantAccountID = "alternate-merchant-account-id"

let payPalRequest = BTPayPalVaultRequest()
payPalClient.payPalRequest = payPalRequest

payPalClient.payPalRequest = BTPayPalCheckoutRequest(amount: "1.34", merchantAccountID: merchantAccountID)

let returnURL = URL(string: "bar://onetouch/v1/success?token=hermes_token")!
payPalClient.handleReturn(returnURL, paymentType: .checkout) { _, _ in }

Expand Down Expand Up @@ -520,6 +530,10 @@ class BTPayPalClient_Tests: XCTestCase {

func testHandleBrowserSwitchReturn_whenBrowserSwitchSucceeds_sendsCorrectParametersForTokenization() {
let returnURL = URL(string: "bar://onetouch/v1/success?token=hermes_token")!

let payPalRequest = BTPayPalVaultRequest()
payPalClient.payPalRequest = payPalRequest

payPalClient.handleReturn(returnURL, paymentType: .vault) { _, _ in }

XCTAssertEqual(mockAPIClient.lastPOSTPath, "/v1/payment_methods/paypal_accounts")
Expand Down Expand Up @@ -591,6 +605,10 @@ class BTPayPalClient_Tests: XCTestCase {
mockAPIClient.cannedResponseBody = BTJSON(value: checkoutResponse as [String : AnyObject])

let returnURL = URL(string: "bar://onetouch/v1/success?token=hermes_token")!

let payPalRequest = BTPayPalVaultRequest()
payPalClient.payPalRequest = payPalRequest

payPalClient.handleReturn(returnURL, paymentType: .checkout) { (tokenizedPayPalAccount, error) in
XCTAssertEqual(tokenizedPayPalAccount!.nonce, "a-nonce")
XCTAssertEqual(tokenizedPayPalAccount!.firstName, "Some")
Expand Down Expand Up @@ -659,6 +677,10 @@ class BTPayPalClient_Tests: XCTestCase {
mockAPIClient.cannedResponseBody = BTJSON(value: checkoutResponse as [String : AnyObject])

let returnURL = URL(string: "bar://onetouch/v1/success?token=hermes_token")!

let payPalRequest = BTPayPalVaultRequest()
payPalClient.payPalRequest = payPalRequest

payPalClient.handleReturn(returnURL, paymentType: .checkout) { (tokenizedPayPalAccount, error) in

let shippingAddress = tokenizedPayPalAccount!.shippingAddress!
Expand Down Expand Up @@ -687,6 +709,10 @@ class BTPayPalClient_Tests: XCTestCase {
mockAPIClient.cannedResponseBody = BTJSON(value: checkoutResponse as [String : AnyObject])

let returnURL = URL(string: "bar://onetouch/v1/success?token=hermes_token")!

let payPalRequest = BTPayPalVaultRequest()
payPalClient.payPalRequest = payPalRequest

payPalClient.handleReturn(returnURL, paymentType: .checkout) { tokenizedPayPalAccount, error in
XCTAssertEqual(tokenizedPayPalAccount!.email, "[email protected]")
}
Expand All @@ -696,6 +722,10 @@ class BTPayPalClient_Tests: XCTestCase {

func testMetadata_whenCheckoutBrowserSwitchIsSuccessful_isPOSTedToServer() {
let returnURL = URL(string: "bar://onetouch/v1/success?token=hermes_token")!

let payPalRequest = BTPayPalVaultRequest()
payPalClient.payPalRequest = payPalRequest

payPalClient.handleReturn(returnURL, paymentType: .checkout) { _, _ in }

XCTAssertEqual(mockAPIClient.lastPOSTPath, "/v1/payment_methods/paypal_accounts")
Expand Down
Loading