Skip to content

Commit

Permalink
Support Encodable POST types (#1151)
Browse files Browse the repository at this point in the history
  • Loading branch information
scannillo authored Dec 11, 2023
1 parent 1528d60 commit 463bf7d
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 44 deletions.
12 changes: 12 additions & 0 deletions Braintree.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
57D94372296CCA2F0079EAB1 /* String+NonceValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D94371296CCA2F0079EAB1 /* String+NonceValidation.swift */; };
800E78C429E0DD5300D1B0FC /* FPTIBatchData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 800E78C329E0DD5300D1B0FC /* FPTIBatchData.swift */; };
800FC544257FDC5100DEE132 /* BTApplePayCardNonce_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 800FC543257FDC5100DEE132 /* BTApplePayCardNonce_Tests.swift */; };
804326BF2B1A5C5B0044E90B /* BTApplePaymentTokensRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804326BE2B1A5C5B0044E90B /* BTApplePaymentTokensRequest.swift */; };
80482F8029D39A1D007E5F50 /* BTThreeDSecureRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80482F7F29D39A1D007E5F50 /* BTThreeDSecureRequest.swift */; };
80482F8229D39BF5007E5F50 /* BTThreeDSecureRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80482F8129D39BF5007E5F50 /* BTThreeDSecureRequestDelegate.swift */; };
80482F8429D3A1D9007E5F50 /* BTThreeDSecureAccountType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80482F8329D3A1D9007E5F50 /* BTThreeDSecureAccountType.swift */; };
Expand All @@ -85,6 +86,8 @@
8053F05929FB2F700076F988 /* URL+IsPayPal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8053F05829FB2F700076F988 /* URL+IsPayPal.swift */; };
80581A8C25531D0A00006F53 /* BTConfiguration+ThreeDSecure_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80581A8B25531D0A00006F53 /* BTConfiguration+ThreeDSecure_Tests.swift */; };
80581B1D2553319C00006F53 /* BTGraphQLHTTP_SSLPinning_IntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80581B1C2553319C00006F53 /* BTGraphQLHTTP_SSLPinning_IntegrationTests.swift */; };
8075CBEE2B1B735200CA6265 /* BTAPIRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8075CBED2B1B735200CA6265 /* BTAPIRequest.swift */; };
80BA3C292B23892700900BBB /* FakeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA3C282B23892700900BBB /* FakeRequest.swift */; };
80A6C6192B21205900416D50 /* UIApplication+URLOpener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80A6C6182B21205900416D50 /* UIApplication+URLOpener.swift */; };
80BA64AC29D788E000E15264 /* BTLocalPaymentResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA64AB29D788E000E15264 /* BTLocalPaymentResult.swift */; };
80BA64B229D7937E00E15264 /* BTLocalPaymentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA64B129D7937E00E15264 /* BTLocalPaymentRequest.swift */; };
Expand Down Expand Up @@ -691,6 +694,7 @@
800E23DC22206A8300C5D22E /* BTThreeDSecureAuthenticateJWT_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureAuthenticateJWT_Tests.swift; sourceTree = "<group>"; };
800E78C329E0DD5300D1B0FC /* FPTIBatchData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPTIBatchData.swift; sourceTree = "<group>"; };
800FC543257FDC5100DEE132 /* BTApplePayCardNonce_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTApplePayCardNonce_Tests.swift; sourceTree = "<group>"; };
804326BE2B1A5C5B0044E90B /* BTApplePaymentTokensRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTApplePaymentTokensRequest.swift; sourceTree = "<group>"; };
80482F7F29D39A1D007E5F50 /* BTThreeDSecureRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureRequest.swift; sourceTree = "<group>"; };
80482F8129D39BF5007E5F50 /* BTThreeDSecureRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureRequestDelegate.swift; sourceTree = "<group>"; };
80482F8329D3A1D9007E5F50 /* BTThreeDSecureAccountType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureAccountType.swift; sourceTree = "<group>"; };
Expand All @@ -703,9 +707,11 @@
80581A8B25531D0A00006F53 /* BTConfiguration+ThreeDSecure_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BTConfiguration+ThreeDSecure_Tests.swift"; sourceTree = "<group>"; };
80581B1C2553319C00006F53 /* BTGraphQLHTTP_SSLPinning_IntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTGraphQLHTTP_SSLPinning_IntegrationTests.swift; sourceTree = "<group>"; };
805FD35B2331780F0000B514 /* BTPostalAddress_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPostalAddress_Tests.swift; sourceTree = "<group>"; };
8075CBED2B1B735200CA6265 /* BTAPIRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAPIRequest.swift; sourceTree = "<group>"; };
80A1EE3D2236AAC600F6218B /* BTThreeDSecureAdditionalInformation_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureAdditionalInformation_Tests.swift; sourceTree = "<group>"; };
80A6C6182B21205900416D50 /* UIApplication+URLOpener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+URLOpener.swift"; sourceTree = "<group>"; };
80B6190C28C9535F00FB5022 /* PayPalCheckout.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PayPalCheckout.xcframework; path = Frameworks/XCFrameworks/PayPalCheckout.xcframework; sourceTree = "<group>"; };
80BA3C282B23892700900BBB /* FakeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeRequest.swift; sourceTree = "<group>"; };
80BA64AB29D788E000E15264 /* BTLocalPaymentResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTLocalPaymentResult.swift; sourceTree = "<group>"; };
80BA64B129D7937E00E15264 /* BTLocalPaymentRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTLocalPaymentRequest.swift; sourceTree = "<group>"; };
80BA64B329D795D000E15264 /* BTLocalPaymentRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTLocalPaymentRequestDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1169,6 +1175,7 @@
BE24C66D28E49A730067B11A /* BTAPIClientError.swift */,
BE24C67228E73E810067B11A /* BTAPIClientHTTPType.swift */,
BE9EC0972899CF040022EC63 /* BTAPIPinnedCertificates.swift */,
8075CBED2B1B735200CA6265 /* BTAPIRequest.swift */,
579DAEC6286E064500FCE87F /* BTAppContextSwitchClient.swift */,
579DAEC4286E04A700FCE87F /* BTAppContextSwitcher.swift */,
BED00CAD28A5419900D74AEC /* BTBinData.swift */,
Expand Down Expand Up @@ -1457,6 +1464,7 @@
BE895C60299433FB008112AB /* BTApplePayCardNonce.swift */,
BE895C6429944BF5008112AB /* BTApplePayClient.swift */,
BE895C6229944BD3008112AB /* BTApplePayError.swift */,
804326BE2B1A5C5B0044E90B /* BTApplePaymentTokensRequest.swift */,
BE7A9643299FC5DE009AB920 /* BTConfiguration+ApplePay.swift */,
);
path = BraintreeApplePay;
Expand Down Expand Up @@ -1514,6 +1522,7 @@
isa = PBXGroup;
children = (
BE54C0342912B6BC009C6CEE /* BTHTTPTestProtocol.swift */,
80BA3C282B23892700900BBB /* FakeRequest.swift */,
428F976426727333001042E1 /* BTMockOpenURLContext.h */,
428F976526727333001042E1 /* BTMockOpenURLContext.m */,
BEBC6F3129380B82004E25A0 /* BTExceptionCatcher.m */,
Expand Down Expand Up @@ -2748,6 +2757,7 @@
8053F05929FB2F700076F988 /* URL+IsPayPal.swift in Sources */,
BEC3F11328A4401E0074DF0F /* BTHTTPError.swift in Sources */,
574891EB286F7E4F0020DA36 /* BTClientMetadataIntegration.swift in Sources */,
8075CBEE2B1B735200CA6265 /* BTAPIRequest.swift in Sources */,
BE698EA428AD2C10001D9B10 /* BTCoreConstants.swift in Sources */,
BE24C67528E7491E0067B11A /* BTAPIClientAuthorization.swift in Sources */,
BE32ACBC2907744400A61FED /* BTCardNetwork.swift in Sources */,
Expand Down Expand Up @@ -2856,6 +2866,7 @@
buildActionMask = 2147483647;
files = (
BEDB820229B109EE00075AF3 /* BTApplePayAnalytics.swift in Sources */,
804326BF2B1A5C5B0044E90B /* BTApplePaymentTokensRequest.swift in Sources */,
BE895C6329944BD3008112AB /* BTApplePayError.swift in Sources */,
BE895C6529944BF5008112AB /* BTApplePayClient.swift in Sources */,
BE7A9644299FC5DE009AB920 /* BTConfiguration+ApplePay.swift in Sources */,
Expand Down Expand Up @@ -3037,6 +3048,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
80BA3C292B23892700900BBB /* FakeRequest.swift in Sources */,
A95229C524FD8F15006F7D25 /* BTVersion_Tests.swift in Sources */,
BE54C0332912B68E009C6CEE /* BTHTTP_Tests.swift in Sources */,
A908437124FD8BB0004134CA /* BTPostalAddress_Tests.swift in Sources */,
Expand Down
24 changes: 2 additions & 22 deletions Sources/BraintreeApplePay/BTApplePayClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,8 @@ import BraintreeCore
self.notifyFailure(with: BTApplePayError.unsupported, completion: completion)
return
}

let metaParameters: [String: String] = [
"source": self.apiClient.metadata.source.stringValue,
"integration": self.apiClient.metadata.integration.stringValue,
"sessionId": self.apiClient.metadata.sessionID
]

let parameters: [String: Any] = [
"applePaymentToken": self.parametersForPaymentToken(token: payment.token),
"_meta": metaParameters
]

let parameters = BTApplePaymentTokensRequest(token: payment.token)

self.apiClient.post("v1/payment_methods/apple_payment_tokens", parameters: parameters) { body, _, error in
if let error {
Expand Down Expand Up @@ -137,17 +128,6 @@ import BraintreeCore
}
}
}

// MARK: - Internal Methods

func parametersForPaymentToken(token: PKPaymentToken) -> [String: Any?] {
[
"paymentData": token.paymentData.base64EncodedString(),
"transactionIdentifier": token.transactionIdentifier,
"paymentInstrumentName": token.paymentMethod.displayName,
"paymentNetwork": token.paymentMethod.network
]
}

// MARK: - Analytics Helper Methods

Expand Down
25 changes: 25 additions & 0 deletions Sources/BraintreeApplePay/BTApplePaymentTokensRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Foundation
import PassKit

/// The POST body for `v1/payment_methods/apple_payment_tokens`
struct BTApplePaymentTokensRequest: Encodable {

private let applePaymentToken: ApplePaymentToken

init(token: PKPaymentToken) {
self.applePaymentToken = ApplePaymentToken(
paymentData: token.paymentData.base64EncodedString(),
transactionIdentifier: token.transactionIdentifier,
paymentInstrumentName: token.paymentMethod.displayName,
paymentNetwork: token.paymentMethod.network?.rawValue
)
}

private struct ApplePaymentToken: Encodable {

let paymentData: String
let transactionIdentifier: String
let paymentInstrumentName: String?
let paymentNetwork: String?
}
}
36 changes: 36 additions & 0 deletions Sources/BraintreeCore/BTAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ import Foundation
}
}

// TODO: - Remove when all POST bodies use Codable, instead of BTJSON/raw dictionaries
/// :nodoc: This method is exposed for internal Braintree use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time.
///
/// Perfom an HTTP POST on a URL composed of the configured from environment and the given path.
Expand Down Expand Up @@ -304,6 +305,40 @@ import Foundation
http(for: httpType)?.post(path, parameters: postParameters, completion: completion)
}
}

/// :nodoc: This method is exposed for internal Braintree use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time.
///
/// Perfom an HTTP POST on a URL composed of the configured from environment and the given path.
/// - Parameters:
/// - path: The endpoint URI path.
/// - parameters: Optional set of query parameters to be encoded with the request.
/// - httpType: The underlying `BTAPIClientHTTPService` of the HTTP request. Defaults to `.gateway`.
/// - completion: A block object to be executed when the request finishes.
/// On success, `body` and `response` will contain the JSON body response and the
/// HTTP response and `error` will be `nil`; on failure, `body` and `response` will be
/// `nil` and `error` will contain the error that occurred.
@_documentation(visibility: private)
public func post(
_ path: String,
parameters: Encodable,
httpType: BTAPIClientHTTPService = .gateway,
completion: @escaping RequestCompletion
) {
fetchOrReturnRemoteConfiguration { [weak self] configuration, error in
guard let self else {
completion(nil, nil, BTAPIClientError.deallocated)
return
}

if let error {
completion(nil, nil, error)
return
}

let postParameters = BTAPIRequest(requestBody: parameters, metadata: metadata, httpType: httpType)
http(for: httpType)?.post(path, parameters: postParameters, completion: completion)
}
}

/// :nodoc: This method is exposed for internal Braintree use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time.
@_documentation(visibility: private)
Expand All @@ -318,6 +353,7 @@ import Foundation

// MARK: Analytics Internal Methods

// TODO: - Remove once all POSTs moved to Encodable
func metadataParametersWith(_ parameters: [String: Any]? = [:], for httpType: BTAPIClientHTTPService) -> [String: Any]? {
switch httpType {
case .gateway:
Expand Down
39 changes: 39 additions & 0 deletions Sources/BraintreeCore/BTAPIRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Foundation

/// An `Encodable` type containing POST body details & metadata params formatted for the BT Gateway & BT GraphQL API
struct BTAPIRequest: Encodable {

private let requestBody: Encodable
private let metadata: BTClientMetadata
private let httpType: BTAPIClientHTTPService

private enum MetadataKeys: String, CodingKey {
case gatewayMetadataKey = "_meta"
case graphQLMetadataKey = "clientSdkMetadata"
}

/// Initialize a `BTAPIRequest` to format a POST body with metadata params for BT APIs.
/// - Parameters:
/// - requestBody: The actual POST body details.
/// - metadata: The metadata details to append into the POST body.
/// - httpType: The Braintree API type for this request.
init(requestBody: Encodable, metadata: BTClientMetadata, httpType: BTAPIClientHTTPService) {
self.requestBody = requestBody
self.metadata = metadata
self.httpType = httpType
}

func encode(to encoder: Encoder) throws {
try requestBody.encode(to: encoder)

var metadataContainer = encoder.container(keyedBy: MetadataKeys.self)
switch httpType {
case .gateway:
let metadataEncoder = metadataContainer.superEncoder(forKey: .gatewayMetadataKey)
try self.metadata.encode(to: metadataEncoder)
case .graphQLAPI:
let metadataEncoder = metadataContainer.superEncoder(forKey: .graphQLMetadataKey)
try self.metadata.encode(to: metadataEncoder)
}
}
}
34 changes: 27 additions & 7 deletions Sources/BraintreeCore/BTClientMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Foundation
/// used. In this case, the source and integration may change over time, while
/// the sessionID should remain constant.
@_documentation(visibility: private)
@objcMembers public class BTClientMetadata: NSObject {
public class BTClientMetadata: Encodable {

/// :nodoc: This property is exposed for internal Braintree use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time.
/// Integration type
Expand All @@ -24,8 +24,13 @@ import Foundation

/// :nodoc: This property is exposed for internal Braintree use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time.
/// Auto-generated UUID
public var sessionID: String
public var sessionID = UUID().uuidString.replacingOccurrences(of: "-", with: "")

public var platform = "iOS"

public var version = BTCoreConstants.braintreeSDKVersion

// TODO: - Remove once all POSTs moved to Codable
/// Additional metadata parameters
var parameters: [String: Any] {
[
Expand All @@ -37,10 +42,25 @@ import Foundation
]
}

override init() {
self.integration = .custom
self.source = .unknown
self.sessionID = UUID().uuidString.replacingOccurrences(of: "-", with: "")
super.init()
private enum CodingKeys: String, CodingKey {
case integration = "integration"
case sessionID = "sessionId"
case source = "source"
case platform = "platform"
case version = "version"
}

init(integration: BTClientMetadataIntegration = .custom, source: BTClientMetadataSource = .unknown) {
self.integration = integration
self.source = source
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(integration.stringValue, forKey: .integration)
try container.encode(sessionID, forKey: .sessionID)
try container.encode(source.stringValue, forKey: .source)
try container.encode(platform, forKey: .platform)
try container.encode(version, forKey: .version)
}
}
23 changes: 8 additions & 15 deletions UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,13 @@ class BTApplePay_Tests: XCTestCase {
XCTFail()
return
}
let metaParameters = lastPostParameters["_meta"] as! NSDictionary
XCTAssertEqual(metaParameters["source"] as? String, "unknown")
XCTAssertEqual(metaParameters["integration"] as? String, "custom")
XCTAssertEqual(metaParameters["sessionId"] as? String, mockAPIClient.metadata.sessionID)

let applePayTokenParams = lastPostParameters["applePaymentToken"] as! NSDictionary
XCTAssertEqual(applePayTokenParams["paymentData"] as? String, Data().base64EncodedString())
XCTAssertEqual(applePayTokenParams["transactionIdentifier"] as? String, "fake-transaction-id")
// The following are expected nil in tests since we cannot mock `PKPaymentToken.paymentMethod`
XCTAssertNil(applePayTokenParams["paymentInstrumentName"])
XCTAssertNil(applePayTokenParams["paymentNetwork"])
}

class MockPKPaymentToken : PKPaymentToken {
Expand All @@ -335,17 +338,7 @@ class BTApplePay_Tests: XCTestCase {
}
override var transactionIdentifier : String {
get {
return "transaction-id"
}
}
override var paymentInstrumentName : String {
get {
return "payment-instrument-name"
}
}
override var paymentNetwork : String {
get {
return "payment-network"
return "fake-transaction-id"
}
}
}
Expand Down
Loading

0 comments on commit 463bf7d

Please sign in to comment.