From 01d4b9acc562b08988c7924bcc8911acb8fb01af Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 1 Dec 2023 11:50:42 -0600 Subject: [PATCH 01/13] Remove redundant APIClient.post() and APIClient.get() methods --- Sources/BraintreeCore/BTAPIClient.swift | 48 +++++++++---------- .../BraintreeTestShared/MockAPIClient.swift | 8 ---- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index 8fabf75531..33ea322914 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -256,36 +256,19 @@ import Foundation /// - 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) - @objc(GET:parameters:completion:) - public func get(_ path: String, parameters: [String: String]? = nil, completion: @escaping RequestCompletion) { - get(path, parameters: parameters, httpType: .gateway, 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. - /// - 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) - @objc(POST:parameters:completion:) - public func post(_ path: String, parameters: [String: Any]? = nil, completion: @escaping RequestCompletion) { - post(path, parameters: parameters, httpType: .gateway, 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) @objc(GET:parameters:httpType:completion:) - public func get(_ path: String, parameters: [String: String]? = nil, httpType: BTAPIClientHTTPService, completion: @escaping RequestCompletion) { + public func get( + _ path: String, + parameters: [String: String]? = nil, + httpType: BTAPIClientHTTPService = .gateway, + completion: @escaping RequestCompletion + ) { fetchOrReturnRemoteConfiguration { [weak self] configuration, error in guard let self else { completion(nil, nil, BTAPIClientError.deallocated) @@ -302,9 +285,24 @@ import Foundation } /// :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) @objc(POST:parameters:httpType:completion:) - public func post(_ path: String, parameters: [String: Any]? = nil, httpType: BTAPIClientHTTPService, completion: @escaping RequestCompletion) { + public func post( + _ path: String, + parameters: [String: Any]? = nil, + httpType: BTAPIClientHTTPService = .gateway, + completion: @escaping RequestCompletion + ) { fetchOrReturnRemoteConfiguration { [weak self] configuration, error in guard let self else { completion(nil, nil, BTAPIClientError.deallocated) diff --git a/UnitTests/BraintreeTestShared/MockAPIClient.swift b/UnitTests/BraintreeTestShared/MockAPIClient.swift index 2d3e2f4c79..68b3ef451c 100644 --- a/UnitTests/BraintreeTestShared/MockAPIClient.swift +++ b/UnitTests/BraintreeTestShared/MockAPIClient.swift @@ -26,14 +26,6 @@ public class MockAPIClient: BTAPIClient { override init?(authorization: String, sendAnalyticsEvent: Bool = false) { super.init(authorization: authorization, sendAnalyticsEvent: sendAnalyticsEvent) } - - public override func get(_ path: String, parameters: [String: String]?, completion completionBlock: ((BTJSON?, HTTPURLResponse?, Error?) -> Void)? = nil) { - self.get(path, parameters: parameters, httpType:.gateway, completion: completionBlock) - } - - public override func post(_ path: String, parameters: [String: Any]?, completion completionBlock: ((BTJSON?, HTTPURLResponse?, Error?) -> Void)? = nil) { - self.post(path, parameters: parameters, httpType:.gateway, completion: completionBlock) - } public override func get(_ path: String, parameters: [String: String]?, httpType: BTAPIClientHTTPService, completion completionBlock: ((BTJSON?, HTTPURLResponse?, Error?) -> Void)? = nil) { lastGETPath = path From d1cd680e14b44c9b6865961c5c7341975ca1eefc Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 1 Dec 2023 13:39:22 -0600 Subject: [PATCH 02/13] WIP - send POST to v1/payment_methods/apple_payment_tokens via Encodable vs dictionary --- Braintree.xcodeproj/project.pbxproj | 4 ++ .../xcshareddata/swiftpm/Package.resolved | 14 ++++ .../BraintreeApplePay/BTApplePayClient.swift | 26 +++---- .../BraintreeApplePay/BTApplePayModel.swift | 71 +++++++++++++++++++ Sources/BraintreeCore/BTAPIClient.swift | 35 +++++++++ 5 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 Sources/BraintreeApplePay/BTApplePayModel.swift diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index b48912fe5b..cf5de276e6 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -75,6 +75,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 /* BTApplePayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804326BE2B1A5C5B0044E90B /* BTApplePayModel.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 */; }; @@ -687,6 +688,7 @@ 800E23DC22206A8300C5D22E /* BTThreeDSecureAuthenticateJWT_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureAuthenticateJWT_Tests.swift; sourceTree = ""; }; 800E78C329E0DD5300D1B0FC /* FPTIBatchData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPTIBatchData.swift; sourceTree = ""; }; 800FC543257FDC5100DEE132 /* BTApplePayCardNonce_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTApplePayCardNonce_Tests.swift; sourceTree = ""; }; + 804326BE2B1A5C5B0044E90B /* BTApplePayModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTApplePayModel.swift; sourceTree = ""; }; 80482F7F29D39A1D007E5F50 /* BTThreeDSecureRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureRequest.swift; sourceTree = ""; }; 80482F8129D39BF5007E5F50 /* BTThreeDSecureRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureRequestDelegate.swift; sourceTree = ""; }; 80482F8329D3A1D9007E5F50 /* BTThreeDSecureAccountType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureAccountType.swift; sourceTree = ""; }; @@ -1443,6 +1445,7 @@ BE895C6429944BF5008112AB /* BTApplePayClient.swift */, BE895C6229944BD3008112AB /* BTApplePayError.swift */, BE7A9643299FC5DE009AB920 /* BTConfiguration+ApplePay.swift */, + 804326BE2B1A5C5B0044E90B /* BTApplePayModel.swift */, ); path = BraintreeApplePay; sourceTree = ""; @@ -2836,6 +2839,7 @@ buildActionMask = 2147483647; files = ( BEDB820229B109EE00075AF3 /* BTApplePayAnalytics.swift in Sources */, + 804326BF2B1A5C5B0044E90B /* BTApplePayModel.swift in Sources */, BE895C6329944BD3008112AB /* BTApplePayError.swift in Sources */, BE895C6529944BF5008112AB /* BTApplePayClient.swift in Sources */, BE7A9644299FC5DE009AB920 /* BTConfiguration+ApplePay.swift in Sources */, diff --git a/Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000000..e4751385cf --- /dev/null +++ b/Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "paypalcheckout-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/paypal/paypalcheckout-ios/", + "state" : { + "revision" : "f477176da4c6780c8586b493c604f0ab74d9be49", + "version" : "1.2.0" + } + } + ], + "version" : 2 +} diff --git a/Sources/BraintreeApplePay/BTApplePayClient.swift b/Sources/BraintreeApplePay/BTApplePayClient.swift index a3c2d33e39..f0393e092f 100644 --- a/Sources/BraintreeApplePay/BTApplePayClient.swift +++ b/Sources/BraintreeApplePay/BTApplePayClient.swift @@ -89,18 +89,20 @@ import BraintreeCore 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 - ] - - self.apiClient.post("v1/payment_methods/apple_payment_tokens", parameters: parameters) { body, _, error in +// 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 codableParams = BTApplePaymentTokensRequest(token: payment.token, metadata: self.apiClient.metadata) + + self.apiClient.post("v1/payment_methods/apple_payment_tokens", parameters: codableParams) { body, _, error in if let error { self.notifyFailure(with: error, completion: completion) return diff --git a/Sources/BraintreeApplePay/BTApplePayModel.swift b/Sources/BraintreeApplePay/BTApplePayModel.swift new file mode 100644 index 0000000000..1204674851 --- /dev/null +++ b/Sources/BraintreeApplePay/BTApplePayModel.swift @@ -0,0 +1,71 @@ +import Foundation +import PassKit +import BraintreeCore + +struct BTApplePaymentTokensRequest: Encodable { + + let applePaymentToken: ApplePaymentToken + let meta: Metadata + + init(token: PKPaymentToken, metadata: BTClientMetadata) { + self.applePaymentToken = ApplePaymentToken( + paymentData: token.paymentData.base64EncodedString(), + transactionIdentifier: token.transactionIdentifier, + paymentInstrumentName: token.paymentMethod.displayName, + paymentNetwork: token.paymentMethod.network?.rawValue + ) + self.meta = Metadata( + source: metadata.source.stringValue, + integration: metadata.integration.stringValue, + sessionID: metadata.sessionID + ) + } + + enum CodingKeys: String, CodingKey { + case applePaymentToken = "applePaymentToken" + case meta = "_meta" + } +} + +struct ApplePaymentToken: Encodable { + + let paymentData: String + let transactionIdentifier: String + let paymentInstrumentName: String? + let paymentNetwork: String? +} + +struct Metadata: Encodable { + + let source: String + let integration: String + let sessionID: String + + enum CodingKeys: String, CodingKey { + case source = "source" + case integration = "integration" + case sessionID = "sessionId" + } +} + +//func parametersForPaymentToken(token: PKPaymentToken) -> [String: Any?] { +// [ +// "paymentData": token.paymentData.base64EncodedString(), +// "transactionIdentifier": token.transactionIdentifier, +// "paymentInstrumentName": token.paymentMethod.displayName, +// "paymentNetwork": token.paymentMethod.network +// ] +//} + + +//let metaParameters: [String: String] = [ +// "source": self.apiClient.metadata.source.stringValue, +// "integration": self.apiClient.metadata.integration.stringValue, +// "sessionId": self.apiClient.metadata.sessionID +//] + +// MAIN +//"{\"applePaymentToken\":{\"paymentData\":\"\",\"paymentInstrumentName\":\"Simulated Instrument\",\"paymentNetwork\":\"Visa\",\"transactionIdentifier\":\"Simulated Identifier\"},\"_meta\":{\"platform\":\"iOS\",\"sessionId\":\"80F9B1235C6A4D23935BC5B2D904AF8B\",\"integration\":\"custom\",\"source\":\"unknown\",\"version\":\"6.10.0\"},\"authorization_fingerprint\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjIwMTgwNDI2MTYtc2FuZGJveCIsImlzcyI6Imh0dHBzOi8vYXBpLnNhbmRib3guYnJhaW50cmVlZ2F0ZXdheS5jb20ifQ.eyJleHAiOjE3MDE1NDE3NjUsImp0aSI6IjI1MzZiMmI0LTk5ODYtNDg1NS04OWJhLWM1ZmQzNjdjNjE1ZCIsInN1YiI6ImRjcHNweTJicndkanIzcW4iLCJpc3MiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwibWVyY2hhbnQiOnsicHVibGljX2lkIjoiZGNwc3B5MmJyd2RqcjNxbiIsInZlcmlmeV9jYXJkX2J5X2RlZmF1bHQiOnRydWV9LCJyaWdodHMiOlsibWFuYWdlX3ZhdWx0Il0sInNjb3BlIjpbIkJyYWludHJlZTpWYXVsdCJdLCJvcHRpb25zIjp7ImN1c3RvbWVyX2lkIjoiMTRENzM1RUQtQjlGMi00OUUzLTk1NkItOTRFMDYzNDdCMzYyIn19.mthqHIasRTDJokuO1ccKbRHnpQL5sA1t8F6rf2nLvaaZGr2qnx6ICP6eoHspZoijfnJEF_a64NzbFYD9pQfpQA?customer_id=\"}" + +// CURRENT MINE +// "{\"_meta\":{\"sessionID\":\"88B10C30FB464E4F83FE6BDF0F6AFE90\",\"integration\":\"custom\",\"source\":\"unknown\"},\"applePaymentToken\":{\"paymentNetwork\":\"Visa\",\"paymentInstrumentName\":\"Simulated Instrument\",\"paymentData\":\"\",\"transactionIdentifier\":\"Simulated Identifier\"},\"authorization_fingerprint\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjIwMTgwNDI2MTYtc2FuZGJveCIsImlzcyI6Imh0dHBzOi8vYXBpLnNhbmRib3guYnJhaW50cmVlZ2F0ZXdheS5jb20ifQ.eyJleHAiOjE3MDE1NDQ1ODYsImp0aSI6ImRlYjI5ODVlLWYxYjAtNGE5NS05OWQ3LTdmNzgxZjE0NzViZSIsInN1YiI6ImRjcHNweTJicndkanIzcW4iLCJpc3MiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwibWVyY2hhbnQiOnsicHVibGljX2lkIjoiZGNwc3B5MmJyd2RqcjNxbiIsInZlcmlmeV9jYXJkX2J5X2RlZmF1bHQiOnRydWV9LCJyaWdodHMiOlsibWFuYWdlX3ZhdWx0Il0sInNjb3BlIjpbIkJyYWludHJlZTpWYXVsdCJdLCJvcHRpb25zIjp7ImN1c3RvbWVyX2lkIjoiOUY3NEUwNTItQTUzNS00NTA1LTg0RUQtNzMxNjA3NDZFMTBGIn19.5mCccRpsEoe0cOHURxVxvBDIUBSMwvSZySwJNo1Ou72mnsJ0tNQGbeUYTrshwZNsHQDkryqX8IrBnp5Q49-kmA?customer_id=\"}" diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index 33ea322914..a618060390 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -284,6 +284,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. @@ -318,6 +319,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 = metadataParametersWith(parameters, for: httpType) + http(for: httpType)?.post(path, parameters: parameters, 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) From 23176f753ce57f3a0b3c3b08289051ce93f29939 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 1 Dec 2023 14:20:33 -0600 Subject: [PATCH 03/13] WIP - move metadata encoding into custom GWEncodable class --- .../BraintreeApplePay/BTApplePayClient.swift | 2 +- .../BraintreeApplePay/BTApplePayModel.swift | 46 ++++++++++++++----- Sources/BraintreeCore/BTAPIClient.swift | 43 ++++++++++++++++- 3 files changed, 77 insertions(+), 14 deletions(-) diff --git a/Sources/BraintreeApplePay/BTApplePayClient.swift b/Sources/BraintreeApplePay/BTApplePayClient.swift index f0393e092f..e1242c82f5 100644 --- a/Sources/BraintreeApplePay/BTApplePayClient.swift +++ b/Sources/BraintreeApplePay/BTApplePayClient.swift @@ -100,7 +100,7 @@ import BraintreeCore // "_meta": metaParameters // ] - let codableParams = BTApplePaymentTokensRequest(token: payment.token, metadata: self.apiClient.metadata) + let codableParams = BTApplePaymentTokensRequest(token: payment.token) self.apiClient.post("v1/payment_methods/apple_payment_tokens", parameters: codableParams) { body, _, error in if let error { diff --git a/Sources/BraintreeApplePay/BTApplePayModel.swift b/Sources/BraintreeApplePay/BTApplePayModel.swift index 1204674851..d39612e424 100644 --- a/Sources/BraintreeApplePay/BTApplePayModel.swift +++ b/Sources/BraintreeApplePay/BTApplePayModel.swift @@ -5,25 +5,33 @@ import BraintreeCore struct BTApplePaymentTokensRequest: Encodable { let applePaymentToken: ApplePaymentToken - let meta: Metadata + // let meta: Metadata - init(token: PKPaymentToken, metadata: BTClientMetadata) { + init(token: PKPaymentToken) { self.applePaymentToken = ApplePaymentToken( paymentData: token.paymentData.base64EncodedString(), transactionIdentifier: token.transactionIdentifier, paymentInstrumentName: token.paymentMethod.displayName, paymentNetwork: token.paymentMethod.network?.rawValue ) - self.meta = Metadata( - source: metadata.source.stringValue, - integration: metadata.integration.stringValue, - sessionID: metadata.sessionID - ) +// self.meta = Metadata( +// integration: metadata.integration.stringValue, +// sessionID: metadata.sessionID, +// source: metadata.source.stringValue +// ) } enum CodingKeys: String, CodingKey { case applePaymentToken = "applePaymentToken" - case meta = "_meta" + //case meta = "_meta" + } + + struct ApplePaymentToken: Encodable { + + let paymentData: String + let transactionIdentifier: String + let paymentInstrumentName: String? + let paymentNetwork: String? } } @@ -35,19 +43,35 @@ struct ApplePaymentToken: Encodable { let paymentNetwork: String? } +public struct GatewayRequestModel: Encodable { + + let actualPostDetails: Encodable + let metadata: Metadata + + public func encode(to encoder: Encoder) throws { + try actualPostDetails.encode(to: encoder) + try metadata.encode(to: encoder) + } +} + struct Metadata: Encodable { - let source: String let integration: String let sessionID: String + let source: String + let platform = "iOS" + let version = BTCoreConstants.braintreeSDKVersion enum CodingKeys: String, CodingKey { - case source = "source" case integration = "integration" case sessionID = "sessionId" + case source = "source" + case platform = "platform" + case version = "version" } } + //func parametersForPaymentToken(token: PKPaymentToken) -> [String: Any?] { // [ // "paymentData": token.paymentData.base64EncodedString(), @@ -68,4 +92,4 @@ struct Metadata: Encodable { //"{\"applePaymentToken\":{\"paymentData\":\"\",\"paymentInstrumentName\":\"Simulated Instrument\",\"paymentNetwork\":\"Visa\",\"transactionIdentifier\":\"Simulated Identifier\"},\"_meta\":{\"platform\":\"iOS\",\"sessionId\":\"80F9B1235C6A4D23935BC5B2D904AF8B\",\"integration\":\"custom\",\"source\":\"unknown\",\"version\":\"6.10.0\"},\"authorization_fingerprint\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjIwMTgwNDI2MTYtc2FuZGJveCIsImlzcyI6Imh0dHBzOi8vYXBpLnNhbmRib3guYnJhaW50cmVlZ2F0ZXdheS5jb20ifQ.eyJleHAiOjE3MDE1NDE3NjUsImp0aSI6IjI1MzZiMmI0LTk5ODYtNDg1NS04OWJhLWM1ZmQzNjdjNjE1ZCIsInN1YiI6ImRjcHNweTJicndkanIzcW4iLCJpc3MiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwibWVyY2hhbnQiOnsicHVibGljX2lkIjoiZGNwc3B5MmJyd2RqcjNxbiIsInZlcmlmeV9jYXJkX2J5X2RlZmF1bHQiOnRydWV9LCJyaWdodHMiOlsibWFuYWdlX3ZhdWx0Il0sInNjb3BlIjpbIkJyYWludHJlZTpWYXVsdCJdLCJvcHRpb25zIjp7ImN1c3RvbWVyX2lkIjoiMTRENzM1RUQtQjlGMi00OUUzLTk1NkItOTRFMDYzNDdCMzYyIn19.mthqHIasRTDJokuO1ccKbRHnpQL5sA1t8F6rf2nLvaaZGr2qnx6ICP6eoHspZoijfnJEF_a64NzbFYD9pQfpQA?customer_id=\"}" // CURRENT MINE -// "{\"_meta\":{\"sessionID\":\"88B10C30FB464E4F83FE6BDF0F6AFE90\",\"integration\":\"custom\",\"source\":\"unknown\"},\"applePaymentToken\":{\"paymentNetwork\":\"Visa\",\"paymentInstrumentName\":\"Simulated Instrument\",\"paymentData\":\"\",\"transactionIdentifier\":\"Simulated Identifier\"},\"authorization_fingerprint\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjIwMTgwNDI2MTYtc2FuZGJveCIsImlzcyI6Imh0dHBzOi8vYXBpLnNhbmRib3guYnJhaW50cmVlZ2F0ZXdheS5jb20ifQ.eyJleHAiOjE3MDE1NDQ1ODYsImp0aSI6ImRlYjI5ODVlLWYxYjAtNGE5NS05OWQ3LTdmNzgxZjE0NzViZSIsInN1YiI6ImRjcHNweTJicndkanIzcW4iLCJpc3MiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwibWVyY2hhbnQiOnsicHVibGljX2lkIjoiZGNwc3B5MmJyd2RqcjNxbiIsInZlcmlmeV9jYXJkX2J5X2RlZmF1bHQiOnRydWV9LCJyaWdodHMiOlsibWFuYWdlX3ZhdWx0Il0sInNjb3BlIjpbIkJyYWludHJlZTpWYXVsdCJdLCJvcHRpb25zIjp7ImN1c3RvbWVyX2lkIjoiOUY3NEUwNTItQTUzNS00NTA1LTg0RUQtNzMxNjA3NDZFMTBGIn19.5mCccRpsEoe0cOHURxVxvBDIUBSMwvSZySwJNo1Ou72mnsJ0tNQGbeUYTrshwZNsHQDkryqX8IrBnp5Q49-kmA?customer_id=\"}" +// "{\"_meta\":{\"version\":\"6.10.0\",\"integration\":\"TEST\",\"sessionId\":\"TEST\",\"source\":\"TEST\",\"platform\":\"iOS\"},\"applePaymentToken\":{\"paymentNetwork\":\"Visa\",\"paymentInstrumentName\":\"Simulated Instrument\",\"paymentData\":\"\",\"transactionIdentifier\":\"Simulated Identifier\"},\"authorization_fingerprint\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjIwMTgwNDI2MTYtc2FuZGJveCIsImlzcyI6Imh0dHBzOi8vYXBpLnNhbmRib3guYnJhaW50cmVlZ2F0ZXdheS5jb20ifQ.eyJleHAiOjE3MDE1NDgyNjgsImp0aSI6Ijg5MjZkMjVmLWE1ZDEtNDMwZi1iNjYzLWM1NzRkYzI3ZGRhMiIsInN1YiI6ImRjcHNweTJicndkanIzcW4iLCJpc3MiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwibWVyY2hhbnQiOnsicHVibGljX2lkIjoiZGNwc3B5MmJyd2RqcjNxbiIsInZlcmlmeV9jYXJkX2J5X2RlZmF1bHQiOnRydWV9LCJyaWdodHMiOlsibWFuYWdlX3ZhdWx0Il0sInNjb3BlIjpbIkJyYWludHJlZTpWYXVsdCJdLCJvcHRpb25zIjp7ImN1c3RvbWVyX2lkIjoiRDE0NUMyNEUtRDI0Ny00MDhCLUIyMUEtOTY2MTU0ODM3RjNBIn19.wVWolNjAXJCThYonwlvzm01zqQfPAJl_YX-WV0n8tXVrHednSweVcZbbjzYzLNqr_HhzT0_Ymlu04zvR5mVJ4g?customer_id=\"}" diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index a618060390..285daf4a4c 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -349,8 +349,8 @@ import Foundation return } - // let postParameters = metadataParametersWith(parameters, for: httpType) - http(for: httpType)?.post(path, parameters: parameters, completion: completion) + let postParameters = metadataParametersWith(parameters, for: httpType) + http(for: httpType)?.post(path, parameters: postParameters!, completion: completion) } } @@ -366,6 +366,10 @@ import Foundation } // MARK: Analytics Internal Methods + + func metadataParametersWith(_ parameters: Encodable, for httpType: BTAPIClientHTTPService) -> Encodable? { + return GatewayRequestModel(actualPostDetails: parameters, metadata: Metadata(integration: "TEST", sessionID: "TEST", source: "TEST")) + } func metadataParametersWith(_ parameters: [String: Any]? = [:], for httpType: BTAPIClientHTTPService) -> [String: Any]? { switch httpType { @@ -499,3 +503,38 @@ import Foundation } } } + +public struct GatewayRequestModel: Encodable { + + private enum MetadataKeys: String, CodingKey { + case metadata = "_meta" + } + + let actualPostDetails: Encodable + let metadata: Metadata + + public func encode(to encoder: Encoder) throws { + try actualPostDetails.encode(to: encoder) + + var metadataContainer = encoder.container(keyedBy: MetadataKeys.self) + let metadataEncoder = metadataContainer.superEncoder(forKey: .metadata) + try self.metadata.encode(to: metadataEncoder) + } +} + +struct Metadata: Encodable { + + let integration: String + let sessionID: String + let source: String + let platform = "iOS" + let version = BTCoreConstants.braintreeSDKVersion + + enum CodingKeys: String, CodingKey { + case integration = "integration" + case sessionID = "sessionId" + case source = "source" + case platform = "platform" + case version = "version" + } +} From 1f97b5d19feeef189d0d24bcfe80738946156f80 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 1 Dec 2023 15:11:05 -0600 Subject: [PATCH 04/13] Add comment for custom encode() implementation reference --- Sources/BraintreeCore/BTAPIClient.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index 285daf4a4c..d13cb13b09 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -516,6 +516,7 @@ public struct GatewayRequestModel: Encodable { public func encode(to encoder: Encoder) throws { try actualPostDetails.encode(to: encoder) + // https://stackoverflow.com/questions/50461744/swift-codable-how-to-encode-top-level-data-into-nested-container var metadataContainer = encoder.container(keyedBy: MetadataKeys.self) let metadataEncoder = metadataContainer.superEncoder(forKey: .metadata) try self.metadata.encode(to: metadataEncoder) From b6537faa28b37549593162932d185a3fc195c10d Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Sat, 2 Dec 2023 07:44:23 -0600 Subject: [PATCH 05/13] Conform BTClientMetadata to Encodable --- .../BraintreeApplePay/BTApplePayModel.swift | 69 +------------------ Sources/BraintreeCore/BTAPIClient.swift | 21 +----- Sources/BraintreeCore/BTClientMetadata.swift | 34 +++++++-- 3 files changed, 31 insertions(+), 93 deletions(-) diff --git a/Sources/BraintreeApplePay/BTApplePayModel.swift b/Sources/BraintreeApplePay/BTApplePayModel.swift index d39612e424..b8b4cad401 100644 --- a/Sources/BraintreeApplePay/BTApplePayModel.swift +++ b/Sources/BraintreeApplePay/BTApplePayModel.swift @@ -1,11 +1,9 @@ import Foundation import PassKit -import BraintreeCore struct BTApplePaymentTokensRequest: Encodable { - let applePaymentToken: ApplePaymentToken - // let meta: Metadata + private let applePaymentToken: ApplePaymentToken init(token: PKPaymentToken) { self.applePaymentToken = ApplePaymentToken( @@ -14,19 +12,9 @@ struct BTApplePaymentTokensRequest: Encodable { paymentInstrumentName: token.paymentMethod.displayName, paymentNetwork: token.paymentMethod.network?.rawValue ) -// self.meta = Metadata( -// integration: metadata.integration.stringValue, -// sessionID: metadata.sessionID, -// source: metadata.source.stringValue -// ) } - enum CodingKeys: String, CodingKey { - case applePaymentToken = "applePaymentToken" - //case meta = "_meta" - } - - struct ApplePaymentToken: Encodable { + private struct ApplePaymentToken: Encodable { let paymentData: String let transactionIdentifier: String @@ -35,59 +23,6 @@ struct BTApplePaymentTokensRequest: Encodable { } } -struct ApplePaymentToken: Encodable { - - let paymentData: String - let transactionIdentifier: String - let paymentInstrumentName: String? - let paymentNetwork: String? -} - -public struct GatewayRequestModel: Encodable { - - let actualPostDetails: Encodable - let metadata: Metadata - - public func encode(to encoder: Encoder) throws { - try actualPostDetails.encode(to: encoder) - try metadata.encode(to: encoder) - } -} - -struct Metadata: Encodable { - - let integration: String - let sessionID: String - let source: String - let platform = "iOS" - let version = BTCoreConstants.braintreeSDKVersion - - enum CodingKeys: String, CodingKey { - case integration = "integration" - case sessionID = "sessionId" - case source = "source" - case platform = "platform" - case version = "version" - } -} - - -//func parametersForPaymentToken(token: PKPaymentToken) -> [String: Any?] { -// [ -// "paymentData": token.paymentData.base64EncodedString(), -// "transactionIdentifier": token.transactionIdentifier, -// "paymentInstrumentName": token.paymentMethod.displayName, -// "paymentNetwork": token.paymentMethod.network -// ] -//} - - -//let metaParameters: [String: String] = [ -// "source": self.apiClient.metadata.source.stringValue, -// "integration": self.apiClient.metadata.integration.stringValue, -// "sessionId": self.apiClient.metadata.sessionID -//] - // MAIN //"{\"applePaymentToken\":{\"paymentData\":\"\",\"paymentInstrumentName\":\"Simulated Instrument\",\"paymentNetwork\":\"Visa\",\"transactionIdentifier\":\"Simulated Identifier\"},\"_meta\":{\"platform\":\"iOS\",\"sessionId\":\"80F9B1235C6A4D23935BC5B2D904AF8B\",\"integration\":\"custom\",\"source\":\"unknown\",\"version\":\"6.10.0\"},\"authorization_fingerprint\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjIwMTgwNDI2MTYtc2FuZGJveCIsImlzcyI6Imh0dHBzOi8vYXBpLnNhbmRib3guYnJhaW50cmVlZ2F0ZXdheS5jb20ifQ.eyJleHAiOjE3MDE1NDE3NjUsImp0aSI6IjI1MzZiMmI0LTk5ODYtNDg1NS04OWJhLWM1ZmQzNjdjNjE1ZCIsInN1YiI6ImRjcHNweTJicndkanIzcW4iLCJpc3MiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwibWVyY2hhbnQiOnsicHVibGljX2lkIjoiZGNwc3B5MmJyd2RqcjNxbiIsInZlcmlmeV9jYXJkX2J5X2RlZmF1bHQiOnRydWV9LCJyaWdodHMiOlsibWFuYWdlX3ZhdWx0Il0sInNjb3BlIjpbIkJyYWludHJlZTpWYXVsdCJdLCJvcHRpb25zIjp7ImN1c3RvbWVyX2lkIjoiMTRENzM1RUQtQjlGMi00OUUzLTk1NkItOTRFMDYzNDdCMzYyIn19.mthqHIasRTDJokuO1ccKbRHnpQL5sA1t8F6rf2nLvaaZGr2qnx6ICP6eoHspZoijfnJEF_a64NzbFYD9pQfpQA?customer_id=\"}" diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index d13cb13b09..68bc49f408 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -368,7 +368,7 @@ import Foundation // MARK: Analytics Internal Methods func metadataParametersWith(_ parameters: Encodable, for httpType: BTAPIClientHTTPService) -> Encodable? { - return GatewayRequestModel(actualPostDetails: parameters, metadata: Metadata(integration: "TEST", sessionID: "TEST", source: "TEST")) + return GatewayRequestModel(actualPostDetails: parameters, metadata: metadata) } func metadataParametersWith(_ parameters: [String: Any]? = [:], for httpType: BTAPIClientHTTPService) -> [String: Any]? { @@ -511,7 +511,7 @@ public struct GatewayRequestModel: Encodable { } let actualPostDetails: Encodable - let metadata: Metadata + let metadata: BTClientMetadata public func encode(to encoder: Encoder) throws { try actualPostDetails.encode(to: encoder) @@ -522,20 +522,3 @@ public struct GatewayRequestModel: Encodable { try self.metadata.encode(to: metadataEncoder) } } - -struct Metadata: Encodable { - - let integration: String - let sessionID: String - let source: String - let platform = "iOS" - let version = BTCoreConstants.braintreeSDKVersion - - enum CodingKeys: String, CodingKey { - case integration = "integration" - case sessionID = "sessionId" - case source = "source" - case platform = "platform" - case version = "version" - } -} diff --git a/Sources/BraintreeCore/BTClientMetadata.swift b/Sources/BraintreeCore/BTClientMetadata.swift index 5af70dc135..ebcf1b9c78 100644 --- a/Sources/BraintreeCore/BTClientMetadata.swift +++ b/Sources/BraintreeCore/BTClientMetadata.swift @@ -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 @@ -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] { [ @@ -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) } } From 20c91423881c4a4b81eac83005524e7ab0883aae Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Sat, 2 Dec 2023 08:21:43 -0600 Subject: [PATCH 06/13] Cleanup - rename classes & file organization --- Braintree.xcodeproj/project.pbxproj | 12 ++++--- .../BraintreeApplePay/BTApplePayClient.swift | 26 ++------------ ...wift => BTApplePaymentTokensRequest.swift} | 3 +- Sources/BraintreeCore/BTAPIClient.swift | 26 +++----------- Sources/BraintreeCore/BTAPIRequest.swift | 34 +++++++++++++++++++ 5 files changed, 50 insertions(+), 51 deletions(-) rename Sources/BraintreeApplePay/{BTApplePayModel.swift => BTApplePaymentTokensRequest.swift} (61%) create mode 100644 Sources/BraintreeCore/BTAPIRequest.swift diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index cf5de276e6..3e92ccbd85 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -75,7 +75,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 /* BTApplePayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804326BE2B1A5C5B0044E90B /* BTApplePayModel.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 */; }; @@ -87,6 +87,7 @@ 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 */; }; 80BA64AC29D788E000E15264 /* BTLocalPaymentResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA64AB29D788E000E15264 /* BTLocalPaymentResult.swift */; }; 80BA64B229D7937E00E15264 /* BTLocalPaymentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA64B129D7937E00E15264 /* BTLocalPaymentRequest.swift */; }; 80BA64B429D795D000E15264 /* BTLocalPaymentRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA64B329D795D000E15264 /* BTLocalPaymentRequestDelegate.swift */; }; @@ -688,7 +689,7 @@ 800E23DC22206A8300C5D22E /* BTThreeDSecureAuthenticateJWT_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureAuthenticateJWT_Tests.swift; sourceTree = ""; }; 800E78C329E0DD5300D1B0FC /* FPTIBatchData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPTIBatchData.swift; sourceTree = ""; }; 800FC543257FDC5100DEE132 /* BTApplePayCardNonce_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTApplePayCardNonce_Tests.swift; sourceTree = ""; }; - 804326BE2B1A5C5B0044E90B /* BTApplePayModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTApplePayModel.swift; sourceTree = ""; }; + 804326BE2B1A5C5B0044E90B /* BTApplePaymentTokensRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTApplePaymentTokensRequest.swift; sourceTree = ""; }; 80482F7F29D39A1D007E5F50 /* BTThreeDSecureRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureRequest.swift; sourceTree = ""; }; 80482F8129D39BF5007E5F50 /* BTThreeDSecureRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureRequestDelegate.swift; sourceTree = ""; }; 80482F8329D3A1D9007E5F50 /* BTThreeDSecureAccountType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureAccountType.swift; sourceTree = ""; }; @@ -701,6 +702,7 @@ 80581A8B25531D0A00006F53 /* BTConfiguration+ThreeDSecure_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BTConfiguration+ThreeDSecure_Tests.swift"; sourceTree = ""; }; 80581B1C2553319C00006F53 /* BTGraphQLHTTP_SSLPinning_IntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTGraphQLHTTP_SSLPinning_IntegrationTests.swift; sourceTree = ""; }; 805FD35B2331780F0000B514 /* BTPostalAddress_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPostalAddress_Tests.swift; sourceTree = ""; }; + 8075CBED2B1B735200CA6265 /* BTAPIRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAPIRequest.swift; sourceTree = ""; }; 80A1EE3D2236AAC600F6218B /* BTThreeDSecureAdditionalInformation_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureAdditionalInformation_Tests.swift; sourceTree = ""; }; 80B6190C28C9535F00FB5022 /* PayPalCheckout.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PayPalCheckout.xcframework; path = Frameworks/XCFrameworks/PayPalCheckout.xcframework; sourceTree = ""; }; 80BA64AB29D788E000E15264 /* BTLocalPaymentResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTLocalPaymentResult.swift; sourceTree = ""; }; @@ -1162,6 +1164,7 @@ BE24C67228E73E810067B11A /* BTAPIClientHTTPType.swift */, 57CBBCE528B02FE80037F4EE /* BTAPIHTTP.swift */, BE9EC0972899CF040022EC63 /* BTAPIPinnedCertificates.swift */, + 8075CBED2B1B735200CA6265 /* BTAPIRequest.swift */, 579DAEC6286E064500FCE87F /* BTAppContextSwitchClient.swift */, 579DAEC4286E04A700FCE87F /* BTAppContextSwitcher.swift */, BED00CAD28A5419900D74AEC /* BTBinData.swift */, @@ -1444,8 +1447,8 @@ BE895C60299433FB008112AB /* BTApplePayCardNonce.swift */, BE895C6429944BF5008112AB /* BTApplePayClient.swift */, BE895C6229944BD3008112AB /* BTApplePayError.swift */, + 804326BE2B1A5C5B0044E90B /* BTApplePaymentTokensRequest.swift */, BE7A9643299FC5DE009AB920 /* BTConfiguration+ApplePay.swift */, - 804326BE2B1A5C5B0044E90B /* BTApplePayModel.swift */, ); path = BraintreeApplePay; sourceTree = ""; @@ -2737,6 +2740,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 */, @@ -2839,7 +2843,7 @@ buildActionMask = 2147483647; files = ( BEDB820229B109EE00075AF3 /* BTApplePayAnalytics.swift in Sources */, - 804326BF2B1A5C5B0044E90B /* BTApplePayModel.swift in Sources */, + 804326BF2B1A5C5B0044E90B /* BTApplePaymentTokensRequest.swift in Sources */, BE895C6329944BD3008112AB /* BTApplePayError.swift in Sources */, BE895C6529944BF5008112AB /* BTApplePayClient.swift in Sources */, BE7A9644299FC5DE009AB920 /* BTConfiguration+ApplePay.swift in Sources */, diff --git a/Sources/BraintreeApplePay/BTApplePayClient.swift b/Sources/BraintreeApplePay/BTApplePayClient.swift index e1242c82f5..ba944e1e0d 100644 --- a/Sources/BraintreeApplePay/BTApplePayClient.swift +++ b/Sources/BraintreeApplePay/BTApplePayClient.swift @@ -88,21 +88,10 @@ 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 codableParams = BTApplePaymentTokensRequest(token: payment.token) + let parameters = BTApplePaymentTokensRequest(token: payment.token) - self.apiClient.post("v1/payment_methods/apple_payment_tokens", parameters: codableParams) { body, _, error in + self.apiClient.post("v1/payment_methods/apple_payment_tokens", parameters: parameters) { body, _, error in if let error { self.notifyFailure(with: error, completion: completion) return @@ -139,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 diff --git a/Sources/BraintreeApplePay/BTApplePayModel.swift b/Sources/BraintreeApplePay/BTApplePaymentTokensRequest.swift similarity index 61% rename from Sources/BraintreeApplePay/BTApplePayModel.swift rename to Sources/BraintreeApplePay/BTApplePaymentTokensRequest.swift index b8b4cad401..8138511b0c 100644 --- a/Sources/BraintreeApplePay/BTApplePayModel.swift +++ b/Sources/BraintreeApplePay/BTApplePaymentTokensRequest.swift @@ -1,6 +1,7 @@ import Foundation import PassKit +/// The POST body for `v1/payment_methods/apple_payment_tokens` struct BTApplePaymentTokensRequest: Encodable { private let applePaymentToken: ApplePaymentToken @@ -27,4 +28,4 @@ struct BTApplePaymentTokensRequest: Encodable { //"{\"applePaymentToken\":{\"paymentData\":\"\",\"paymentInstrumentName\":\"Simulated Instrument\",\"paymentNetwork\":\"Visa\",\"transactionIdentifier\":\"Simulated Identifier\"},\"_meta\":{\"platform\":\"iOS\",\"sessionId\":\"80F9B1235C6A4D23935BC5B2D904AF8B\",\"integration\":\"custom\",\"source\":\"unknown\",\"version\":\"6.10.0\"},\"authorization_fingerprint\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjIwMTgwNDI2MTYtc2FuZGJveCIsImlzcyI6Imh0dHBzOi8vYXBpLnNhbmRib3guYnJhaW50cmVlZ2F0ZXdheS5jb20ifQ.eyJleHAiOjE3MDE1NDE3NjUsImp0aSI6IjI1MzZiMmI0LTk5ODYtNDg1NS04OWJhLWM1ZmQzNjdjNjE1ZCIsInN1YiI6ImRjcHNweTJicndkanIzcW4iLCJpc3MiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwibWVyY2hhbnQiOnsicHVibGljX2lkIjoiZGNwc3B5MmJyd2RqcjNxbiIsInZlcmlmeV9jYXJkX2J5X2RlZmF1bHQiOnRydWV9LCJyaWdodHMiOlsibWFuYWdlX3ZhdWx0Il0sInNjb3BlIjpbIkJyYWludHJlZTpWYXVsdCJdLCJvcHRpb25zIjp7ImN1c3RvbWVyX2lkIjoiMTRENzM1RUQtQjlGMi00OUUzLTk1NkItOTRFMDYzNDdCMzYyIn19.mthqHIasRTDJokuO1ccKbRHnpQL5sA1t8F6rf2nLvaaZGr2qnx6ICP6eoHspZoijfnJEF_a64NzbFYD9pQfpQA?customer_id=\"}" // CURRENT MINE -// "{\"_meta\":{\"version\":\"6.10.0\",\"integration\":\"TEST\",\"sessionId\":\"TEST\",\"source\":\"TEST\",\"platform\":\"iOS\"},\"applePaymentToken\":{\"paymentNetwork\":\"Visa\",\"paymentInstrumentName\":\"Simulated Instrument\",\"paymentData\":\"\",\"transactionIdentifier\":\"Simulated Identifier\"},\"authorization_fingerprint\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjIwMTgwNDI2MTYtc2FuZGJveCIsImlzcyI6Imh0dHBzOi8vYXBpLnNhbmRib3guYnJhaW50cmVlZ2F0ZXdheS5jb20ifQ.eyJleHAiOjE3MDE1NDgyNjgsImp0aSI6Ijg5MjZkMjVmLWE1ZDEtNDMwZi1iNjYzLWM1NzRkYzI3ZGRhMiIsInN1YiI6ImRjcHNweTJicndkanIzcW4iLCJpc3MiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwibWVyY2hhbnQiOnsicHVibGljX2lkIjoiZGNwc3B5MmJyd2RqcjNxbiIsInZlcmlmeV9jYXJkX2J5X2RlZmF1bHQiOnRydWV9LCJyaWdodHMiOlsibWFuYWdlX3ZhdWx0Il0sInNjb3BlIjpbIkJyYWludHJlZTpWYXVsdCJdLCJvcHRpb25zIjp7ImN1c3RvbWVyX2lkIjoiRDE0NUMyNEUtRDI0Ny00MDhCLUIyMUEtOTY2MTU0ODM3RjNBIn19.wVWolNjAXJCThYonwlvzm01zqQfPAJl_YX-WV0n8tXVrHednSweVcZbbjzYzLNqr_HhzT0_Ymlu04zvR5mVJ4g?customer_id=\"}" +// "{\"_meta\":{\"version\":\"6.10.0\",\"integration\":\"custom\",\"sessionId\":\"64F33ABEF7F54E4BA80E566A37ECFEC5\",\"source\":\"unknown\",\"platform\":\"iOS\"},\"applePaymentToken\":{\"paymentInstrumentName\":\"Simulated Instrument\",\"paymentNetwork\":\"Visa\",\"paymentData\":\"\",\"transactionIdentifier\":\"Simulated Identifier\"},\"authorization_fingerprint\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjIwMTgwNDI2MTYtc2FuZGJveCIsImlzcyI6Imh0dHBzOi8vYXBpLnNhbmRib3guYnJhaW50cmVlZ2F0ZXdheS5jb20ifQ.eyJleHAiOjE3MDE2MTExNTUsImp0aSI6IjZhMDkyODVlLWMwNWMtNDY0ZS05NmNmLTE4M2Y1YTBlZjJjMiIsInN1YiI6ImRjcHNweTJicndkanIzcW4iLCJpc3MiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwibWVyY2hhbnQiOnsicHVibGljX2lkIjoiZGNwc3B5MmJyd2RqcjNxbiIsInZlcmlmeV9jYXJkX2J5X2RlZmF1bHQiOnRydWV9LCJyaWdodHMiOlsibWFuYWdlX3ZhdWx0Il0sInNjb3BlIjpbIkJyYWludHJlZTpWYXVsdCJdLCJvcHRpb25zIjp7ImN1c3RvbWVyX2lkIjoiRjU4QzRFMjItMjg5RS00Q0FCLUFCOTQtMjU3QjNFNkJDODdDIn19.eUbfM7vY9shdmRdYmMLcRWM5uSWWQopW1pSK1OtDaJjKwCvpOVGRtJWQOugzIjumC-O7QAoj6__LW7372ctMJg?customer_id=\"}" diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index 68bc49f408..ac1b58937c 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -350,7 +350,7 @@ import Foundation } let postParameters = metadataParametersWith(parameters, for: httpType) - http(for: httpType)?.post(path, parameters: postParameters!, completion: completion) + http(for: httpType)?.post(path, parameters: postParameters, completion: completion) } } @@ -367,10 +367,11 @@ import Foundation // MARK: Analytics Internal Methods - func metadataParametersWith(_ parameters: Encodable, for httpType: BTAPIClientHTTPService) -> Encodable? { - return GatewayRequestModel(actualPostDetails: parameters, metadata: metadata) + func metadataParametersWith(_ parameters: Encodable, for httpType: BTAPIClientHTTPService) -> Encodable { + return BTAPIRequest(requestBody: parameters, metadata: metadata, httpType: httpType) } + // TODO: - Remove once all POSTs moved to Encodable func metadataParametersWith(_ parameters: [String: Any]? = [:], for httpType: BTAPIClientHTTPService) -> [String: Any]? { switch httpType { case .gateway: @@ -503,22 +504,3 @@ import Foundation } } } - -public struct GatewayRequestModel: Encodable { - - private enum MetadataKeys: String, CodingKey { - case metadata = "_meta" - } - - let actualPostDetails: Encodable - let metadata: BTClientMetadata - - public func encode(to encoder: Encoder) throws { - try actualPostDetails.encode(to: encoder) - - // https://stackoverflow.com/questions/50461744/swift-codable-how-to-encode-top-level-data-into-nested-container - var metadataContainer = encoder.container(keyedBy: MetadataKeys.self) - let metadataEncoder = metadataContainer.superEncoder(forKey: .metadata) - try self.metadata.encode(to: metadataEncoder) - } -} diff --git a/Sources/BraintreeCore/BTAPIRequest.swift b/Sources/BraintreeCore/BTAPIRequest.swift new file mode 100644 index 0000000000..ffecdb3725 --- /dev/null +++ b/Sources/BraintreeCore/BTAPIRequest.swift @@ -0,0 +1,34 @@ +import Foundation + +/// An `Encodable` type containing POST body details & additional 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" + } + + 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) + + // https://stackoverflow.com/questions/50461744/swift-codable-how-to-encode-top-level-data-into-nested-container + var metadataContainer = encoder.container(keyedBy: MetadataKeys.self) + if httpType == .gateway { + let metadataEncoder = metadataContainer.superEncoder(forKey: .gatewayMetadataKey) + try self.metadata.encode(to: metadataEncoder) + } else if httpType == .graphQLAPI { + let metadataEncoder = metadataContainer.superEncoder(forKey: .graphQLMetadataKey) + try self.metadata.encode(to: metadataEncoder) + } + } +} From c60265c0d302ac884937d4fa5b108f940a87d62b Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Sat, 2 Dec 2023 10:11:03 -0600 Subject: [PATCH 07/13] WIP + TODO for updating tests --- .../BTApplePay_Tests.swift | 31 ++++++++++--------- .../BraintreeTestShared/MockAPIClient.swift | 11 +++++++ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift b/UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift index 40c3f60fe4..728b44ea45 100644 --- a/UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift +++ b/UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift @@ -321,10 +321,21 @@ 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) + + // TODO: - Move meta assertions out of here and into APIClient level to make sure it applies to all API requests + +// 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 { @@ -335,17 +346,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" } } } diff --git a/UnitTests/BraintreeTestShared/MockAPIClient.swift b/UnitTests/BraintreeTestShared/MockAPIClient.swift index 68b3ef451c..a981a051c8 100644 --- a/UnitTests/BraintreeTestShared/MockAPIClient.swift +++ b/UnitTests/BraintreeTestShared/MockAPIClient.swift @@ -49,6 +49,17 @@ public class MockAPIClient: BTAPIClient { completionBlock(cannedResponseBody, cannedHTTPURLResponse, cannedResponseError) } + public override func post(_ path: String, parameters: Encodable, httpType: BTAPIClientHTTPService = .gateway, completion completionBlock: ((BTJSON?, HTTPURLResponse?, Error?) -> Void)? = nil) { + lastPOSTPath = path + lastPOSTParameters = try? parameters.toDictionary() + lastPOSTAPIClientHTTPType = httpType + + guard let completionBlock = completionBlock else { + return + } + completionBlock(cannedResponseBody, cannedHTTPURLResponse, cannedResponseError) + } + public override func fetchOrReturnRemoteConfiguration(_ completionBlock: @escaping (BTConfiguration?, Error?) -> Void) { guard let responseBody = cannedConfigurationResponseBody else { completionBlock(nil, cannedConfigurationResponseError) From d2c3d5451736b693b30258a50b85e7bdd77ee030 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 8 Dec 2023 11:41:15 -0600 Subject: [PATCH 08/13] Cleanup unit tests --- Braintree.xcodeproj/project.pbxproj | 4 ++ .../BTApplePaymentTokensRequest.swift | 6 -- Sources/BraintreeCore/BTAPIClient.swift | 6 +- Sources/BraintreeCore/BTAPIRequest.swift | 8 ++- .../BTApplePay_Tests.swift | 7 --- .../BTAPIClient_Tests.swift | 57 +++++++++++++++++++ .../Helpers/FakeRequest.swift | 6 ++ 7 files changed, 74 insertions(+), 20 deletions(-) create mode 100644 UnitTests/BraintreeCoreTests/Helpers/FakeRequest.swift diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index ff63de2257..914a5262b2 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -87,6 +87,7 @@ 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 */; }; 80BA64AC29D788E000E15264 /* BTLocalPaymentResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA64AB29D788E000E15264 /* BTLocalPaymentResult.swift */; }; 80BA64B229D7937E00E15264 /* BTLocalPaymentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA64B129D7937E00E15264 /* BTLocalPaymentRequest.swift */; }; 80BA64B429D795D000E15264 /* BTLocalPaymentRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA64B329D795D000E15264 /* BTLocalPaymentRequestDelegate.swift */; }; @@ -708,6 +709,7 @@ 8075CBED2B1B735200CA6265 /* BTAPIRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAPIRequest.swift; sourceTree = ""; }; 80A1EE3D2236AAC600F6218B /* BTThreeDSecureAdditionalInformation_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureAdditionalInformation_Tests.swift; sourceTree = ""; }; 80B6190C28C9535F00FB5022 /* PayPalCheckout.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PayPalCheckout.xcframework; path = Frameworks/XCFrameworks/PayPalCheckout.xcframework; sourceTree = ""; }; + 80BA3C282B23892700900BBB /* FakeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeRequest.swift; sourceTree = ""; }; 80BA64AB29D788E000E15264 /* BTLocalPaymentResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTLocalPaymentResult.swift; sourceTree = ""; }; 80BA64B129D7937E00E15264 /* BTLocalPaymentRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTLocalPaymentRequest.swift; sourceTree = ""; }; 80BA64B329D795D000E15264 /* BTLocalPaymentRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTLocalPaymentRequestDelegate.swift; sourceTree = ""; }; @@ -1517,6 +1519,7 @@ isa = PBXGroup; children = ( BE54C0342912B6BC009C6CEE /* BTHTTPTestProtocol.swift */, + 80BA3C282B23892700900BBB /* FakeRequest.swift */, 428F976426727333001042E1 /* BTMockOpenURLContext.h */, 428F976526727333001042E1 /* BTMockOpenURLContext.m */, BEBC6F3129380B82004E25A0 /* BTExceptionCatcher.m */, @@ -3041,6 +3044,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 */, diff --git a/Sources/BraintreeApplePay/BTApplePaymentTokensRequest.swift b/Sources/BraintreeApplePay/BTApplePaymentTokensRequest.swift index 8138511b0c..cd48b322d3 100644 --- a/Sources/BraintreeApplePay/BTApplePaymentTokensRequest.swift +++ b/Sources/BraintreeApplePay/BTApplePaymentTokensRequest.swift @@ -23,9 +23,3 @@ struct BTApplePaymentTokensRequest: Encodable { let paymentNetwork: String? } } - -// MAIN -//"{\"applePaymentToken\":{\"paymentData\":\"\",\"paymentInstrumentName\":\"Simulated Instrument\",\"paymentNetwork\":\"Visa\",\"transactionIdentifier\":\"Simulated Identifier\"},\"_meta\":{\"platform\":\"iOS\",\"sessionId\":\"80F9B1235C6A4D23935BC5B2D904AF8B\",\"integration\":\"custom\",\"source\":\"unknown\",\"version\":\"6.10.0\"},\"authorization_fingerprint\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjIwMTgwNDI2MTYtc2FuZGJveCIsImlzcyI6Imh0dHBzOi8vYXBpLnNhbmRib3guYnJhaW50cmVlZ2F0ZXdheS5jb20ifQ.eyJleHAiOjE3MDE1NDE3NjUsImp0aSI6IjI1MzZiMmI0LTk5ODYtNDg1NS04OWJhLWM1ZmQzNjdjNjE1ZCIsInN1YiI6ImRjcHNweTJicndkanIzcW4iLCJpc3MiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwibWVyY2hhbnQiOnsicHVibGljX2lkIjoiZGNwc3B5MmJyd2RqcjNxbiIsInZlcmlmeV9jYXJkX2J5X2RlZmF1bHQiOnRydWV9LCJyaWdodHMiOlsibWFuYWdlX3ZhdWx0Il0sInNjb3BlIjpbIkJyYWludHJlZTpWYXVsdCJdLCJvcHRpb25zIjp7ImN1c3RvbWVyX2lkIjoiMTRENzM1RUQtQjlGMi00OUUzLTk1NkItOTRFMDYzNDdCMzYyIn19.mthqHIasRTDJokuO1ccKbRHnpQL5sA1t8F6rf2nLvaaZGr2qnx6ICP6eoHspZoijfnJEF_a64NzbFYD9pQfpQA?customer_id=\"}" - -// CURRENT MINE -// "{\"_meta\":{\"version\":\"6.10.0\",\"integration\":\"custom\",\"sessionId\":\"64F33ABEF7F54E4BA80E566A37ECFEC5\",\"source\":\"unknown\",\"platform\":\"iOS\"},\"applePaymentToken\":{\"paymentInstrumentName\":\"Simulated Instrument\",\"paymentNetwork\":\"Visa\",\"paymentData\":\"\",\"transactionIdentifier\":\"Simulated Identifier\"},\"authorization_fingerprint\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjIwMTgwNDI2MTYtc2FuZGJveCIsImlzcyI6Imh0dHBzOi8vYXBpLnNhbmRib3guYnJhaW50cmVlZ2F0ZXdheS5jb20ifQ.eyJleHAiOjE3MDE2MTExNTUsImp0aSI6IjZhMDkyODVlLWMwNWMtNDY0ZS05NmNmLTE4M2Y1YTBlZjJjMiIsInN1YiI6ImRjcHNweTJicndkanIzcW4iLCJpc3MiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwibWVyY2hhbnQiOnsicHVibGljX2lkIjoiZGNwc3B5MmJyd2RqcjNxbiIsInZlcmlmeV9jYXJkX2J5X2RlZmF1bHQiOnRydWV9LCJyaWdodHMiOlsibWFuYWdlX3ZhdWx0Il0sInNjb3BlIjpbIkJyYWludHJlZTpWYXVsdCJdLCJvcHRpb25zIjp7ImN1c3RvbWVyX2lkIjoiRjU4QzRFMjItMjg5RS00Q0FCLUFCOTQtMjU3QjNFNkJDODdDIn19.eUbfM7vY9shdmRdYmMLcRWM5uSWWQopW1pSK1OtDaJjKwCvpOVGRtJWQOugzIjumC-O7QAoj6__LW7372ctMJg?customer_id=\"}" diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index a1cbcacfd7..f8cfc8ca2c 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -335,7 +335,7 @@ import Foundation return } - let postParameters = metadataParametersWith(parameters, for: httpType) + let postParameters = BTAPIRequest(requestBody: parameters, metadata: metadata, httpType: httpType) http(for: httpType)?.post(path, parameters: postParameters, completion: completion) } } @@ -352,10 +352,6 @@ import Foundation } // MARK: Analytics Internal Methods - - func metadataParametersWith(_ parameters: Encodable, for httpType: BTAPIClientHTTPService) -> Encodable { - return BTAPIRequest(requestBody: parameters, metadata: metadata, httpType: httpType) - } // TODO: - Remove once all POSTs moved to Encodable func metadataParametersWith(_ parameters: [String: Any]? = [:], for httpType: BTAPIClientHTTPService) -> [String: Any]? { diff --git a/Sources/BraintreeCore/BTAPIRequest.swift b/Sources/BraintreeCore/BTAPIRequest.swift index ffecdb3725..d90481b82e 100644 --- a/Sources/BraintreeCore/BTAPIRequest.swift +++ b/Sources/BraintreeCore/BTAPIRequest.swift @@ -1,6 +1,6 @@ import Foundation -/// An `Encodable` type containing POST body details & additional metadata params formatted for the BT Gateway & BT GraphQL API +/// An `Encodable` type containing POST body details & metadata params formatted for the BT Gateway & BT GraphQL API struct BTAPIRequest: Encodable { private let requestBody: Encodable @@ -12,6 +12,11 @@ struct BTAPIRequest: Encodable { 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 @@ -21,7 +26,6 @@ struct BTAPIRequest: Encodable { func encode(to encoder: Encoder) throws { try requestBody.encode(to: encoder) - // https://stackoverflow.com/questions/50461744/swift-codable-how-to-encode-top-level-data-into-nested-container var metadataContainer = encoder.container(keyedBy: MetadataKeys.self) if httpType == .gateway { let metadataEncoder = metadataContainer.superEncoder(forKey: .gatewayMetadataKey) diff --git a/UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift b/UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift index 728b44ea45..b6d77930d3 100644 --- a/UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift +++ b/UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift @@ -322,13 +322,6 @@ class BTApplePay_Tests: XCTestCase { return } - // TODO: - Move meta assertions out of here and into APIClient level to make sure it applies to all API requests - -// 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") diff --git a/UnitTests/BraintreeCoreTests/BTAPIClient_Tests.swift b/UnitTests/BraintreeCoreTests/BTAPIClient_Tests.swift index 8a1f71a446..47a92a6f4f 100644 --- a/UnitTests/BraintreeCoreTests/BTAPIClient_Tests.swift +++ b/UnitTests/BraintreeCoreTests/BTAPIClient_Tests.swift @@ -450,6 +450,63 @@ class BTAPIClient_Tests: XCTestCase { waitForExpectations(timeout: 2) } + + func testPOST_withEncodableParams_whenUsingGateway_includesMetadata() { + let apiClient = BTAPIClient(authorization: "development_tokenization_key") + let mockHTTP = FakeHTTP.fakeHTTP() + let metadata = apiClient?.metadata + + apiClient?.http = mockHTTP + apiClient?.configurationHTTP = mockHTTP + mockHTTP.stubRequest(withMethod: "GET", toEndpoint: "/client_api/v1/configuration", respondWith: [] as [Any?], statusCode: 200) + + let postParameters = FakeRequest(testValue: "fake-value") + + let expectation = expectation(description: "POST callback") + apiClient?.post("/", parameters: postParameters, httpType: .gateway) { _, _, _ in + XCTAssertEqual(mockHTTP.lastRequestParameters?["testValue"] as? String, "fake-value") + + let metaParameters = mockHTTP.lastRequestParameters?["_meta"] as? [String: Any] + XCTAssertEqual(metaParameters?["integration"] as? String, metadata?.integration.stringValue) + XCTAssertEqual(metaParameters?["source"] as? String, metadata?.source.stringValue) + XCTAssertEqual(metaParameters?["sessionId"] as? String, metadata?.sessionID) + expectation.fulfill() + } + + waitForExpectations(timeout: 2) + } + + func testPOST_withEncodableParams_whenUsingGraphQLAPI_includesMetadata() { + let apiClient = BTAPIClient(authorization: "development_tokenization_key") + let mockGraphQLHTTP = FakeGraphQLHTTP.fakeHTTP() + let mockHTTP = FakeHTTP.fakeHTTP() + let metadata = apiClient?.metadata + let mockResponse = [ + "graphQL": [ + "url": "graphql://graphql", + "features": ["tokenize_credit_cards"] + ] as [String: Any] + ] + + apiClient?.graphQLHTTP = mockGraphQLHTTP + apiClient?.configurationHTTP = mockHTTP + mockHTTP.stubRequest(withMethod: "GET", toEndpoint: "/client_api/v1/configuration", respondWith: mockResponse, statusCode: 200) + + let postParameters = FakeRequest(testValue: "fake-value") + + let expectation = expectation(description: "POST callback") + apiClient?.post("/", parameters: postParameters, httpType: .graphQLAPI) { _, _, _ in + XCTAssertEqual(mockHTTP.lastRequestParameters?["testValue"] as? String, "fake-value") + + let clientSdkMetadata = mockGraphQLHTTP.lastRequestParameters?["clientSdkMetadata"] as? [String: String] + XCTAssertEqual(clientSdkMetadata?["integration"] as? String, metadata?.integration.stringValue) + XCTAssertEqual(clientSdkMetadata?["source"] as? String, metadata?.source.stringValue) + XCTAssertEqual(clientSdkMetadata?["sessionId"] as? String, metadata?.sessionID) + expectation.fulfill() + } + + waitForExpectations(timeout: 2) + } // MARK: - Timeouts diff --git a/UnitTests/BraintreeCoreTests/Helpers/FakeRequest.swift b/UnitTests/BraintreeCoreTests/Helpers/FakeRequest.swift new file mode 100644 index 0000000000..72d3874581 --- /dev/null +++ b/UnitTests/BraintreeCoreTests/Helpers/FakeRequest.swift @@ -0,0 +1,6 @@ +import Foundation + +struct FakeRequest: Encodable { + + let testValue: String +} From 09be532f531e1a9ca018035baefb3ca9703ab711 Mon Sep 17 00:00:00 2001 From: scannillo <35243507+scannillo@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:47:04 -0600 Subject: [PATCH 09/13] Delete Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index e4751385cf..0000000000 --- a/Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,14 +0,0 @@ -{ - "pins" : [ - { - "identity" : "paypalcheckout-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/paypal/paypalcheckout-ios/", - "state" : { - "revision" : "f477176da4c6780c8586b493c604f0ab74d9be49", - "version" : "1.2.0" - } - } - ], - "version" : 2 -} From ede168f352a09692a308ac38f1902240c9afaf03 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 8 Dec 2023 11:48:45 -0600 Subject: [PATCH 10/13] Cleanup - remove newline --- UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift b/UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift index b6d77930d3..9f1b005110 100644 --- a/UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift +++ b/UnitTests/BraintreeApplePayTests/BTApplePay_Tests.swift @@ -328,7 +328,6 @@ class BTApplePay_Tests: XCTestCase { // The following are expected nil in tests since we cannot mock `PKPaymentToken.paymentMethod` XCTAssertNil(applePayTokenParams["paymentInstrumentName"]) XCTAssertNil(applePayTokenParams["paymentNetwork"]) - } class MockPKPaymentToken : PKPaymentToken { From 3d8caab97bd445dfb5f0f16e5fa84ee8b10eb09a Mon Sep 17 00:00:00 2001 From: scannillo <35243507+scannillo@users.noreply.github.com> Date: Fri, 8 Dec 2023 12:37:04 -0600 Subject: [PATCH 11/13] Delete Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index e4751385cf..0000000000 --- a/Braintree.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,14 +0,0 @@ -{ - "pins" : [ - { - "identity" : "paypalcheckout-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/paypal/paypalcheckout-ios/", - "state" : { - "revision" : "f477176da4c6780c8586b493c604f0ab74d9be49", - "version" : "1.2.0" - } - } - ], - "version" : 2 -} From 262b88651a652b5bf831ba719c365fb23f292ebc Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 8 Dec 2023 12:56:09 -0600 Subject: [PATCH 12/13] Fixup - address copy-paste typo in test --- UnitTests/BraintreeCoreTests/BTAPIClient_Tests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitTests/BraintreeCoreTests/BTAPIClient_Tests.swift b/UnitTests/BraintreeCoreTests/BTAPIClient_Tests.swift index 47a92a6f4f..3d2c5bfa46 100644 --- a/UnitTests/BraintreeCoreTests/BTAPIClient_Tests.swift +++ b/UnitTests/BraintreeCoreTests/BTAPIClient_Tests.swift @@ -496,7 +496,7 @@ class BTAPIClient_Tests: XCTestCase { let expectation = expectation(description: "POST callback") apiClient?.post("/", parameters: postParameters, httpType: .graphQLAPI) { _, _, _ in - XCTAssertEqual(mockHTTP.lastRequestParameters?["testValue"] as? String, "fake-value") + XCTAssertEqual(mockGraphQLHTTP.lastRequestParameters?["testValue"] as? String, "fake-value") let clientSdkMetadata = mockGraphQLHTTP.lastRequestParameters?["clientSdkMetadata"] as? [String: String] XCTAssertEqual(clientSdkMetadata?["integration"] as? String, metadata?.integration.stringValue) From e2d52c40bf3c001650c7ef2d2a05b0fabcc2f9ba Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Mon, 11 Dec 2023 10:49:49 -0600 Subject: [PATCH 13/13] PR Feedback - prefer switch to if-let syntax on enum --- Sources/BraintreeCore/BTAPIRequest.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/BraintreeCore/BTAPIRequest.swift b/Sources/BraintreeCore/BTAPIRequest.swift index d90481b82e..0d76e1bb9a 100644 --- a/Sources/BraintreeCore/BTAPIRequest.swift +++ b/Sources/BraintreeCore/BTAPIRequest.swift @@ -27,10 +27,11 @@ struct BTAPIRequest: Encodable { try requestBody.encode(to: encoder) var metadataContainer = encoder.container(keyedBy: MetadataKeys.self) - if httpType == .gateway { + switch httpType { + case .gateway: let metadataEncoder = metadataContainer.superEncoder(forKey: .gatewayMetadataKey) try self.metadata.encode(to: metadataEncoder) - } else if httpType == .graphQLAPI { + case .graphQLAPI: let metadataEncoder = metadataContainer.superEncoder(forKey: .graphQLMetadataKey) try self.metadata.encode(to: metadataEncoder) }