From 3e5785a710406278fac5424164162fd0834d6e47 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Tue, 8 Aug 2023 22:29:34 -0500 Subject: [PATCH 01/30] WIP - use new RestRequest for analytics --- .../CorePayments/AnalyticsEventRequest.swift | 3 -- .../CorePayments/Networking/APIClient.swift | 47 ++++++++++++++++++- .../Networking/AnalyticsService.swift | 36 +++++++++++--- Sources/CorePayments/Networking/HTTP.swift | 31 ++++++++++-- .../Networking/Protocols/APIRequest.swift | 30 ------------ 5 files changed, 103 insertions(+), 44 deletions(-) diff --git a/Sources/CorePayments/AnalyticsEventRequest.swift b/Sources/CorePayments/AnalyticsEventRequest.swift index 01b026478..92db849bd 100644 --- a/Sources/CorePayments/AnalyticsEventRequest.swift +++ b/Sources/CorePayments/AnalyticsEventRequest.swift @@ -18,9 +18,6 @@ struct AnalyticsEventRequest: APIRequest { var body: Data? // api.sandbox.paypal.com does not currently send FPTI events to BigQuery/Looker - func toURLRequest(environment: Environment) -> URLRequest? { - composeURLRequest(environment: .live) - } } struct EmptyResponse: Decodable { } diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index 10d9a0446..85ecf3829 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -29,7 +29,52 @@ public class APIClient { /// :nodoc: This method is exposed for internal PayPal use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time. public func fetch(request: T) async throws -> (T.ResponseType) { - let httpResponse = try await http.performRequest(request) + let url = try constructURL(path: request.path, queryParameters: request.queryParameters) + + let httpRequest = HTTPRequest( + url: url, + method: request.method, + body: request.body, + headers: request.headers + ) + + let httpResponse = try await http.performRequest(httpRequest) return try HTTPResponseParser().parse(httpResponse, as: T.ResponseType.self) } + + func fetch(request: RESTRequest) async throws -> HTTPResponse { + let url = try constructURL(path: request.path, queryParameters: request.queryParameters ?? [:]) // cleaner way + + let httpRequest = HTTPRequest( + url: url, + method: request.method, + body: request.body, + headers: request.headers + ) + + return try await http.performRequest(httpRequest) + } + + private func constructURL(path: String, queryParameters: [String: String]) throws -> URL { + let urlString = coreConfig.environment.baseURL.appendingPathComponent(path) + var urlComponents = URLComponents(url: urlString, resolvingAgainstBaseURL: false) + + queryParameters.forEach { + urlComponents?.queryItems?.append(URLQueryItem(name: $0.key, value: $0.value)) + } + + guard let url = urlComponents?.url else { + throw CorePaymentsError.clientIDNotFoundError // fix + } + + return url + } +} + +public struct RESTRequest { + public var path: String + public var method: HTTPMethod + public var headers: [HTTPHeader: String] + public var queryParameters: [String: String]? + public var body: Data? } diff --git a/Sources/CorePayments/Networking/AnalyticsService.swift b/Sources/CorePayments/Networking/AnalyticsService.swift index 056dd0e97..677cfc44e 100644 --- a/Sources/CorePayments/Networking/AnalyticsService.swift +++ b/Sources/CorePayments/Networking/AnalyticsService.swift @@ -6,23 +6,23 @@ public struct AnalyticsService { // MARK: - Internal Properties private let coreConfig: CoreConfig - private let http: HTTP + private let apiClient: APIClient private let orderID: String // MARK: - Initializer public init(coreConfig: CoreConfig, orderID: String) { self.coreConfig = coreConfig - self.http = HTTP(coreConfig: coreConfig) + self.apiClient = APIClient(coreConfig: CoreConfig(clientID: coreConfig.clientID, environment: .live)) self.orderID = orderID } // MARK: - Internal Initializer /// Exposed for testing - init(coreConfig: CoreConfig, orderID: String, http: HTTP) { + init(coreConfig: CoreConfig, orderID: String, apiClient: APIClient) { self.coreConfig = coreConfig - self.http = http + self.apiClient = apiClient self.orderID = orderID } @@ -45,16 +45,38 @@ public struct AnalyticsService { let clientID = coreConfig.clientID let eventData = AnalyticsEventData( - environment: http.coreConfig.environment.toString, + environment: coreConfig.environment.toString, eventName: name, clientID: clientID, orderID: orderID ) - let analyticsEventRequest = try AnalyticsEventRequest(eventData: eventData) - let (_) = try await http.performRequest(analyticsEventRequest) + let (_) = try await TrackingEventsAPI().sendEvent(with: eventData) } catch { NSLog("[PayPal SDK] Failed to send analytics: %@", error.localizedDescription) } } } + +class TrackingEventsAPI { + + func sendEvent(with analyticsEventData: AnalyticsEventData) async throws -> HTTPResponse { + let apiClient = APIClient(coreConfig: CoreConfig(clientID: analyticsEventData.clientID, environment: .live)) + + // encode the body -- todo move + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + let body = try encoder.encode(analyticsEventData) // handle with special + + let restRequest = RESTRequest( + path: "v1/tracking/events", + method: .post, + headers: [.contentType: "application/json"], + queryParameters: nil, + body: body + ) + + return try await apiClient.fetch(request: restRequest) + // skip HTTP parsing! + } +} diff --git a/Sources/CorePayments/Networking/HTTP.swift b/Sources/CorePayments/Networking/HTTP.swift index f882e5ef2..858ebd889 100644 --- a/Sources/CorePayments/Networking/HTTP.swift +++ b/Sources/CorePayments/Networking/HTTP.swift @@ -14,9 +14,26 @@ class HTTP { self.coreConfig = coreConfig } - func performRequest(_ request: any APIRequest) async throws -> HTTPResponse { - guard let urlRequest = request.toURLRequest(environment: coreConfig.environment) else { - throw APIClientError.invalidURLRequestError +// func performRequest(_ request: any APIRequest) async throws -> HTTPResponse { +// guard let urlRequest = request.toURLRequest(environment: coreConfig.environment) else { +// throw APIClientError.invalidURLRequestError +// } +// +// let (data, response) = try await urlSession.performRequest(with: urlRequest) +// guard let response = response as? HTTPURLResponse else { +// throw APIClientError.invalidURLResponseError +// } +// +// return HTTPResponse(status: response.statusCode, body: data) +// } + + func performRequest(_ httpRequest: HTTPRequest) async throws -> HTTPResponse { + var urlRequest = URLRequest(url: httpRequest.url) + urlRequest.httpMethod = httpRequest.method.rawValue + urlRequest.httpBody = httpRequest.body + + httpRequest.headers.forEach { key, value in + urlRequest.addValue(value, forHTTPHeaderField: key.rawValue) } let (data, response) = try await urlSession.performRequest(with: urlRequest) @@ -27,3 +44,11 @@ class HTTP { return HTTPResponse(status: response.statusCode, body: data) } } + +struct HTTPRequest { + + let url: URL + let method: HTTPMethod + let body: Data? + let headers: [HTTPHeader: String] +} diff --git a/Sources/CorePayments/Networking/Protocols/APIRequest.swift b/Sources/CorePayments/Networking/Protocols/APIRequest.swift index 49f9451ec..24e318c58 100644 --- a/Sources/CorePayments/Networking/Protocols/APIRequest.swift +++ b/Sources/CorePayments/Networking/Protocols/APIRequest.swift @@ -8,8 +8,6 @@ public protocol APIRequest { var headers: [HTTPHeader: String] { get } var queryParameters: [String: String] { get } var body: Data? { get } - - func toURLRequest(environment: Environment) -> URLRequest? } public extension APIRequest { @@ -17,32 +15,4 @@ public extension APIRequest { var queryParameters: [String: String] { [:] } var body: Data? { nil } - - // Default implementation vends response from helper function - func toURLRequest(environment: Environment) -> URLRequest? { - composeURLRequest(environment: environment) - } - - func composeURLRequest(environment: Environment) -> URLRequest? { - let completeUrl = environment.baseURL.appendingPathComponent(path) - var urlComponents = URLComponents(url: completeUrl, resolvingAgainstBaseURL: false) - - queryParameters.forEach { - urlComponents?.queryItems?.append(URLQueryItem(name: $0.key, value: $0.value)) - } - - guard let url = urlComponents?.url else { - return nil - } - - var request = URLRequest(url: url) - request.httpMethod = method.rawValue - request.httpBody = body - - headers.forEach { key, value in - request.addValue(value, forHTTPHeaderField: key.rawValue) - } - - return request - } } From dcae37bf34bdda47056d5315bf0f8c3e056488d6 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Tue, 8 Aug 2023 23:48:06 -0500 Subject: [PATCH 02/30] Refactor ConfirmPaymentSourceRequest to use custom encoder & ConfirmPaymentSourceAPI --- .../ConfirmPaymentSourceRequest.swift | 243 +++++++++++------- Sources/CardPayments/CardClient.swift | 11 +- .../CorePayments/Networking/APIClient.swift | 10 +- Sources/CorePayments/Networking/HTTP.swift | 2 +- .../Networking/HTTPResponse.swift | 2 +- .../Networking/HTTPResponseParser.swift | 6 +- 6 files changed, 162 insertions(+), 112 deletions(-) diff --git a/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift b/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift index 015f3e72b..3cfdae791 100644 --- a/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift +++ b/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift @@ -3,133 +3,176 @@ import Foundation import CorePayments #endif -/// Describes request to confirm a payment source (approve an order) -struct ConfirmPaymentSourceRequest: APIRequest { - - private let orderID: String - private let pathFormat: String = "/v2/checkout/orders/%@/confirm-payment-source" - private let base64EncodedCredentials: String - var jsonEncoder: JSONEncoder - - /// Creates a request to attach a payment source to a specific order. - /// In order to use this initializer, the `paymentSource` parameter has to - /// contain the entire dictionary as it exists underneath the `payment_source` key. - init( - clientID: String, - cardRequest: CardRequest, - encoder: JSONEncoder = JSONEncoder() // exposed for test injection - ) throws { - self.jsonEncoder = encoder - var confirmPaymentSource = ConfirmPaymentSource() +class CheckoutOrdersAPI { + + let coreConfig: CoreConfig + + init(coreConfig: CoreConfig) { + self.coreConfig = coreConfig + } - confirmPaymentSource.applicationContext = ApplicationContext( - returnUrl: PayPalCoreConstants.callbackURLScheme + "://card/success", - cancelUrl: PayPalCoreConstants.callbackURLScheme + "://card/cancel" - ) - - confirmPaymentSource.paymentSource = PaymentSource( - card: cardRequest.card, - scaType: cardRequest.sca - ) + func confirmPaymentSource(clientID: String, cardRequest: CardRequest) async throws -> ConfirmPaymentSourceResponse { + let apiClient = APIClient(coreConfig: coreConfig) - self.orderID = cardRequest.orderID - self.base64EncodedCredentials = Data(clientID.appending(":").utf8).base64EncodedString() - path = String(format: pathFormat, orderID) + let confirmData = ConfirmPaymentSourceRequest(cardRequest: cardRequest) - jsonEncoder.keyEncodingStrategy = .convertToSnakeCase - do { - body = try jsonEncoder.encode(confirmPaymentSource) - } catch { - throw CardClientError.encodingError - } + let base64EncodedCredentials = Data(clientID.appending(":").utf8).base64EncodedString() + + // encode the body -- todo move + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + let body = try encoder.encode(confirmData) // handle with special + + let restRequest = RESTRequest( + path: "/v2/checkout/orders/\(cardRequest.orderID)/confirm-payment-source", + method: .post, + headers: [ + .contentType: "application/json", .acceptLanguage: "en_US", + .authorization: "Basic \(base64EncodedCredentials)" + ], + queryParameters: nil, + body: body + ) - // TODO - The complexity in this `init` signals to reconsider our use/design of the `APIRequest` protocol. - // Existing pattern doesn't provide clear, testable interface for encoding JSON POST bodies. + let httpResponse = try await apiClient.fetch(request: restRequest) + return try HTTPResponseParser().parse(httpResponse, as: ConfirmPaymentSourceResponse.self) } +} + + +//{ +// "payment_source": { +// "card": { +// "number": "4111111111111111", +// "expiry": "2020-02", +// "name": "John Doe", +// "billing_address": { +// "address_line_1": "2211 N First Street", +// "address_line_2": "Building 17", +// "admin_area_2": "San Jose", +// "admin_area_1": "CA", +// "postal_code": "95131", +// "country_code": "US" +// }, +// "attributes": { +// "customer": { +// "id": "wxj1234" +// }, +// "vault": { +// "store_in_vault": "ON_SUCCESS" +// }, +// "verification": { +// "method": "SCA_WHEN_REQUIRED" +// } +// } +// } +// }, +// "application_context": { +// "return_url": "return_url", +// "cancel_url": "return_url" +// } +//} + +/// Describes request to confirm a payment source (approve an order) +struct ConfirmPaymentSourceRequest: Encodable { - // MARK: - APIRequest - - typealias ResponseType = ConfirmPaymentSourceResponse - - var path: String - var method: HTTPMethod = .post - var body: Data? + // MARK: - Coding Keys - var headers: [HTTPHeader: String] { - [ - .contentType: "application/json", .acceptLanguage: "en_US", - .authorization: "Basic \(base64EncodedCredentials)" - ] + enum TopLevelKeys: String, CodingKey { + case paymentSource = "payment_source" + case applicationContext = "application_context" } - private struct ConfirmPaymentSource: Encodable { - - var paymentSource: PaymentSource? - var applicationContext: ApplicationContext? + enum ApplicationContextKeys: String, CodingKey { + case returnURL = "return_url" + case cancelURL = "cancel_url" } - private struct ApplicationContext: Encodable { - - let returnUrl: String - let cancelUrl: String + enum PaymentSourceKeys: String, CodingKey { + case card } - enum CodingKeys: String, CodingKey { + enum CardKeys: String, CodingKey { case number case expiry - case securityCode - case name case billingAddress + case name case attributes } - private struct EncodedCard: Encodable { - - let number: String - let securityCode: String - let billingAddress: Address? - let name: String? - let expiry: String - let attributes: Attributes? - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(number, forKey: .number) - try container.encode(expiry, forKey: .expiry) - try container.encode(securityCode, forKey: .securityCode) - try container.encode(name, forKey: .name) - try container.encode(billingAddress, forKey: .billingAddress) - try container.encode(attributes, forKey: .attributes) - } + enum BillingAddressKeys: String, CodingKey { + case addressLine1 = "address_line_1" + case addressLine2 = "address_line_2" + case adminArea1 = "admin_area_1" + case adminArea2 = "admin_area_2" + case postalCode + case countryCode } - private struct Verification: Encodable { - - let method: String + enum AttributesKeys: String, CodingKey { + case customer + case vault + case verification } - private struct Attributes: Encodable { - - let verification: Verification - - init(verificationMethod: String) { - self.verification = Verification(method: verificationMethod) - } + enum CustomerKeys: String, CodingKey { + case id } - private struct PaymentSource: Encodable { + enum VaultKeys: String, CodingKey { + case storeInVault + } + + enum VerificationKeys: String, CodingKey { + case method + } + + // MARK: - Properties + + let returnURL = PayPalCoreConstants.callbackURLScheme + "://card/success" + let cancelURL = PayPalCoreConstants.callbackURLScheme + "://card/cancel" + + let cardRequest: CardRequest + + // MARK: - Initializer + + init(cardRequest: CardRequest) { + self.cardRequest = cardRequest + } + + // MARK: - Custom Encoder + + func encode(to encoder: Encoder) throws { + var topLevel = encoder.container(keyedBy: TopLevelKeys.self) - let card: EncodedCard + var applicationContext = topLevel.nestedContainer(keyedBy: ApplicationContextKeys.self, forKey: .applicationContext) + try applicationContext.encode(returnURL, forKey: .returnURL) + try applicationContext.encode(cancelURL, forKey: .cancelURL) - init(card: Card, scaType: SCA) { - self.card = EncodedCard( - number: card.number, - securityCode: card.securityCode, - billingAddress: card.billingAddress, - name: card.cardholderName, - expiry: "\(card.expirationYear)-\(card.expirationMonth)", - attributes: Attributes(verificationMethod: scaType.rawValue) - ) + var paymentSource = topLevel.nestedContainer(keyedBy: PaymentSourceKeys.self, forKey: .paymentSource) + var card = paymentSource.nestedContainer(keyedBy: CardKeys.self, forKey: .card) + try card.encode(cardRequest.card.number, forKey: .number) + try card.encode("\(cardRequest.card.expirationYear)-\(cardRequest.card.expirationMonth)", forKey: .expiry) + try card.encode(cardRequest.card.cardholderName, forKey: .name) + + if let cardBillingInfo = cardRequest.card.billingAddress { + var billingAddress = card.nestedContainer(keyedBy: BillingAddressKeys.self, forKey: .billingAddress) + try billingAddress.encode(cardBillingInfo.addressLine1, forKey: .addressLine1) + try billingAddress.encode(cardBillingInfo.addressLine2, forKey: .addressLine2) + try billingAddress.encode(cardBillingInfo.postalCode, forKey: .postalCode) + try billingAddress.encode(cardBillingInfo.region, forKey: .adminArea1) + try billingAddress.encode(cardBillingInfo.locality, forKey: .adminArea2) + try billingAddress.encode(cardBillingInfo.countryCode, forKey: .countryCode) } + var attributes = card.nestedContainer(keyedBy: AttributesKeys.self, forKey: .attributes) + var customer = attributes.nestedContainer(keyedBy: CustomerKeys.self, forKey: .customer) + try customer.encode("fake-customer-id", forKey: .id) // TODO + + var vault = attributes.nestedContainer(keyedBy: VaultKeys.self, forKey: .vault) + try vault.encode("ON_SUCCESS", forKey: .storeInVault) // TODO + + var verification = attributes.nestedContainer(keyedBy: VerificationKeys.self, forKey: .verification) + try verification.encode(cardRequest.sca.rawValue, forKey: .method) //TODO } + } diff --git a/Sources/CardPayments/CardClient.swift b/Sources/CardPayments/CardClient.swift index 093860be7..211eda2e8 100644 --- a/Sources/CardPayments/CardClient.swift +++ b/Sources/CardPayments/CardClient.swift @@ -8,7 +8,7 @@ public class CardClient: NSObject { public weak var delegate: CardDelegate? - private let apiClient: APIClient + private let checkoutOrdersAPI: CheckoutOrdersAPI private let config: CoreConfig private let webAuthenticationSession: WebAuthenticationSession private var analyticsService: AnalyticsService? @@ -17,14 +17,14 @@ public class CardClient: NSObject { /// - Parameter config: The CoreConfig object public init(config: CoreConfig) { self.config = config - self.apiClient = APIClient(coreConfig: config) + self.checkoutOrdersAPI = CheckoutOrdersAPI(coreConfig: config) self.webAuthenticationSession = WebAuthenticationSession() } /// For internal use for testing/mocking purpose - init(config: CoreConfig, apiClient: APIClient, webAuthenticationSession: WebAuthenticationSession) { + init(config: CoreConfig, checkoutOrdersAPI: CheckoutOrdersAPI, webAuthenticationSession: WebAuthenticationSession) { self.config = config - self.apiClient = apiClient + self.checkoutOrdersAPI = checkoutOrdersAPI self.webAuthenticationSession = webAuthenticationSession } @@ -40,8 +40,7 @@ public class CardClient: NSObject { analyticsService?.sendEvent("card-payments:3ds:started") Task { do { - let confirmPaymentRequest = try ConfirmPaymentSourceRequest(clientID: config.clientID, cardRequest: request) - let (result) = try await apiClient.fetch(request: confirmPaymentRequest) + let result = try await checkoutOrdersAPI.confirmPaymentSource(clientID: config.clientID, cardRequest: request) if let url: String = result.links?.first(where: { $0.rel == "payer-action" })?.href { analyticsService?.sendEvent("card-payments:3ds:confirm-payment-source:challenge-required") diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index 85ecf3829..6dde9b843 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -42,7 +42,7 @@ public class APIClient { return try HTTPResponseParser().parse(httpResponse, as: T.ResponseType.self) } - func fetch(request: RESTRequest) async throws -> HTTPResponse { + public func fetch(request: RESTRequest) async throws -> HTTPResponse { let url = try constructURL(path: request.path, queryParameters: request.queryParameters ?? [:]) // cleaner way let httpRequest = HTTPRequest( @@ -77,4 +77,12 @@ public struct RESTRequest { public var headers: [HTTPHeader: String] public var queryParameters: [String: String]? public var body: Data? + + public init(path: String, method: HTTPMethod, headers: [HTTPHeader : String], queryParameters: [String : String]? = nil, body: Data? = nil) { + self.path = path + self.method = method + self.headers = headers + self.queryParameters = queryParameters + self.body = body + } } diff --git a/Sources/CorePayments/Networking/HTTP.swift b/Sources/CorePayments/Networking/HTTP.swift index 858ebd889..1131c8355 100644 --- a/Sources/CorePayments/Networking/HTTP.swift +++ b/Sources/CorePayments/Networking/HTTP.swift @@ -45,7 +45,7 @@ class HTTP { } } -struct HTTPRequest { +public struct HTTPRequest { let url: URL let method: HTTPMethod diff --git a/Sources/CorePayments/Networking/HTTPResponse.swift b/Sources/CorePayments/Networking/HTTPResponse.swift index 3d1e5db66..f01a9674d 100644 --- a/Sources/CorePayments/Networking/HTTPResponse.swift +++ b/Sources/CorePayments/Networking/HTTPResponse.swift @@ -1,6 +1,6 @@ import Foundation -struct HTTPResponse { +public struct HTTPResponse { let status: Int let body: Data? diff --git a/Sources/CorePayments/Networking/HTTPResponseParser.swift b/Sources/CorePayments/Networking/HTTPResponseParser.swift index 1c8868e65..0b7a0bbf5 100644 --- a/Sources/CorePayments/Networking/HTTPResponseParser.swift +++ b/Sources/CorePayments/Networking/HTTPResponseParser.swift @@ -1,15 +1,15 @@ import Foundation -class HTTPResponseParser { +public class HTTPResponseParser { private let decoder: JSONDecoder - init(decoder: JSONDecoder = JSONDecoder()) { // exposed for test injection + public init(decoder: JSONDecoder = JSONDecoder()) { // exposed for test injection self.decoder = decoder decoder.keyDecodingStrategy = .convertFromSnakeCase } - func parse(_ httpResponse: HTTPResponse, as type: T.Type) throws -> T { + public func parse(_ httpResponse: HTTPResponse, as type: T.Type) throws -> T { guard let data = httpResponse.body else { throw APIClientError.noResponseDataError } From 5cd493aeb972530956e2771e3c5f96571b57ba0b Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Wed, 9 Aug 2023 00:00:11 -0500 Subject: [PATCH 03/30] Move new API classes & RESTRequest into own files --- PayPal.xcodeproj/project.pbxproj | 14 ++- .../APIRequests/CheckoutOrdersAPI.swift | 40 +++++++ .../ConfirmPaymentSourceRequest.swift | 104 ++++++------------ .../CorePayments/Networking/APIClient.swift | 16 --- .../Networking/AnalyticsService.swift | 33 +----- .../CorePayments/Networking/RESTRequest.swift | 24 ++++ .../Networking/TrackingEventsAPI.swift | 24 ++++ 7 files changed, 139 insertions(+), 116 deletions(-) create mode 100644 Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift create mode 100644 Sources/CorePayments/Networking/RESTRequest.swift create mode 100644 Sources/CorePayments/Networking/TrackingEventsAPI.swift diff --git a/PayPal.xcodeproj/project.pbxproj b/PayPal.xcodeproj/project.pbxproj index f21e004e8..c00c5d25e 100644 --- a/PayPal.xcodeproj/project.pbxproj +++ b/PayPal.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -49,6 +49,9 @@ 80DBC9DE29C3B57200462539 /* PayPalNativeShippingAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DBC9DD29C3B57200462539 /* PayPalNativeShippingAddress.swift */; }; 80DBC9E029C3B8A800462539 /* PayPalNativeShippingMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DBC9DF29C3B8A800462539 /* PayPalNativeShippingMethod.swift */; }; 80DCC59E2719DB6F00EC7C5A /* CardClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DCC59D2719DB6F00EC7C5A /* CardClientError.swift */; }; + 80E2FDBE2A83528B0045593D /* CheckoutOrdersAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E2FDBD2A83528B0045593D /* CheckoutOrdersAPI.swift */; }; + 80E2FDC12A83535A0045593D /* TrackingEventsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E2FDBF2A8353550045593D /* TrackingEventsAPI.swift */; }; + 80E2FDC32A8354AD0045593D /* RESTRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E2FDC22A8354AD0045593D /* RESTRequest.swift */; }; 80E643832A1EBBD2008FD705 /* HTTPResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E643822A1EBBD2008FD705 /* HTTPResponse.swift */; }; 80E74400270E40F300BACECA /* FakeRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E743FF270E40F300BACECA /* FakeRequests.swift */; }; 80E8DAE126B8784600FAFC3F /* Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E8DAE026B8784600FAFC3F /* Card.swift */; }; @@ -238,6 +241,9 @@ 80DBC9DD29C3B57200462539 /* PayPalNativeShippingAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalNativeShippingAddress.swift; sourceTree = ""; }; 80DBC9DF29C3B8A800462539 /* PayPalNativeShippingMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalNativeShippingMethod.swift; sourceTree = ""; }; 80DCC59D2719DB6F00EC7C5A /* CardClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardClientError.swift; sourceTree = ""; }; + 80E2FDBD2A83528B0045593D /* CheckoutOrdersAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckoutOrdersAPI.swift; sourceTree = ""; }; + 80E2FDBF2A8353550045593D /* TrackingEventsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingEventsAPI.swift; sourceTree = ""; }; + 80E2FDC22A8354AD0045593D /* RESTRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRequest.swift; sourceTree = ""; }; 80E643822A1EBBD2008FD705 /* HTTPResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPResponse.swift; sourceTree = ""; }; 80E743F8270E40CE00BACECA /* TestShared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TestShared.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 80E743FF270E40F300BACECA /* FakeRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeRequests.swift; sourceTree = ""; }; @@ -549,6 +555,7 @@ children = ( 06CE009F26F3DF100000CC46 /* ConfirmPaymentSourceRequest.swift */, 06CE00A226F3E32A0000CC46 /* ConfirmPaymentSourceResponse.swift */, + 80E2FDBD2A83528B0045593D /* CheckoutOrdersAPI.swift */, ); path = APIRequests; sourceTree = ""; @@ -691,8 +698,10 @@ E646231928369B71008AC8E1 /* GraphQL */, BEA100E626EF9EDA0036A6A5 /* APIClient.swift */, 804E628529380B04004B9FEF /* AnalyticsService.swift */, + 80E2FDBF2A8353550045593D /* TrackingEventsAPI.swift */, 804E62812937EBCE004B9FEF /* HTTP.swift */, 80E643822A1EBBD2008FD705 /* HTTPResponse.swift */, + 80E2FDC22A8354AD0045593D /* RESTRequest.swift */, 807BF58E2A2A5D19002F32B3 /* HTTPResponseParser.swift */, BEA100EF26EFA7C20036A6A5 /* APIClientError.swift */, BC04836E27B2FB3600FA7B46 /* URLSessionProtocol.swift */, @@ -1291,11 +1300,13 @@ BEA100EC26EFA7790036A6A5 /* HTTPMethod.swift in Sources */, BEA100EE26EFA7990036A6A5 /* HTTPHeader.swift in Sources */, CB4BE27E2847EA7D00EA2DD1 /* WebAuthenticationSession.swift in Sources */, + 80E2FDC12A83535A0045593D /* TrackingEventsAPI.swift in Sources */, BEA100F226EFA7DE0036A6A5 /* Environment.swift in Sources */, BEA100F026EFA7C20036A6A5 /* APIClientError.swift in Sources */, 804E628629380B04004B9FEF /* AnalyticsService.swift in Sources */, BEA100E926EFA1EE0036A6A5 /* APIRequest.swift in Sources */, BC04837427B2FC7300FA7B46 /* URLSession+URLSessionProtocol.swift in Sources */, + 80E2FDC32A8354AD0045593D /* RESTRequest.swift in Sources */, 3DC42BA927187E8300B71645 /* ErrorResponse.swift in Sources */, 804E62822937EBCE004B9FEF /* HTTP.swift in Sources */, BC04836F27B2FB3600FA7B46 /* URLSessionProtocol.swift in Sources */, @@ -1343,6 +1354,7 @@ buildActionMask = 2147483647; files = ( 06CE009B26F3D5A40000CC46 /* CardClient.swift in Sources */, + 80E2FDBE2A83528B0045593D /* CheckoutOrdersAPI.swift in Sources */, 8048D28C270B9DE00072214A /* ConfirmPaymentSourceResponse.swift in Sources */, 3D1763A22720722A00652E1C /* CardResult.swift in Sources */, BC0A82A5270B9533006E9A21 /* ConfirmPaymentSourceRequest.swift in Sources */, diff --git a/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift b/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift new file mode 100644 index 000000000..b0492152b --- /dev/null +++ b/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift @@ -0,0 +1,40 @@ +import Foundation +#if canImport(CorePayments) +import CorePayments +#endif + +class CheckoutOrdersAPI { + + let coreConfig: CoreConfig + + init(coreConfig: CoreConfig) { + self.coreConfig = coreConfig + } + + func confirmPaymentSource(clientID: String, cardRequest: CardRequest) async throws -> ConfirmPaymentSourceResponse { + let apiClient = APIClient(coreConfig: coreConfig) + + let confirmData = ConfirmPaymentSourceRequest(cardRequest: cardRequest) + + let base64EncodedCredentials = Data(clientID.appending(":").utf8).base64EncodedString() + + // encode the body -- todo move + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + let body = try encoder.encode(confirmData) // handle with special + + let restRequest = RESTRequest( + path: "/v2/checkout/orders/\(cardRequest.orderID)/confirm-payment-source", + method: .post, + headers: [ + .contentType: "application/json", .acceptLanguage: "en_US", + .authorization: "Basic \(base64EncodedCredentials)" + ], + queryParameters: nil, + body: body + ) + + let httpResponse = try await apiClient.fetch(request: restRequest) + return try HTTPResponseParser().parse(httpResponse, as: ConfirmPaymentSourceResponse.self) + } +} diff --git a/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift b/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift index 3cfdae791..1b1f92847 100644 --- a/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift +++ b/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift @@ -3,76 +3,6 @@ import Foundation import CorePayments #endif -class CheckoutOrdersAPI { - - let coreConfig: CoreConfig - - init(coreConfig: CoreConfig) { - self.coreConfig = coreConfig - } - - func confirmPaymentSource(clientID: String, cardRequest: CardRequest) async throws -> ConfirmPaymentSourceResponse { - let apiClient = APIClient(coreConfig: coreConfig) - - let confirmData = ConfirmPaymentSourceRequest(cardRequest: cardRequest) - - let base64EncodedCredentials = Data(clientID.appending(":").utf8).base64EncodedString() - - // encode the body -- todo move - let encoder = JSONEncoder() - encoder.keyEncodingStrategy = .convertToSnakeCase - let body = try encoder.encode(confirmData) // handle with special - - let restRequest = RESTRequest( - path: "/v2/checkout/orders/\(cardRequest.orderID)/confirm-payment-source", - method: .post, - headers: [ - .contentType: "application/json", .acceptLanguage: "en_US", - .authorization: "Basic \(base64EncodedCredentials)" - ], - queryParameters: nil, - body: body - ) - - let httpResponse = try await apiClient.fetch(request: restRequest) - return try HTTPResponseParser().parse(httpResponse, as: ConfirmPaymentSourceResponse.self) - } -} - - -//{ -// "payment_source": { -// "card": { -// "number": "4111111111111111", -// "expiry": "2020-02", -// "name": "John Doe", -// "billing_address": { -// "address_line_1": "2211 N First Street", -// "address_line_2": "Building 17", -// "admin_area_2": "San Jose", -// "admin_area_1": "CA", -// "postal_code": "95131", -// "country_code": "US" -// }, -// "attributes": { -// "customer": { -// "id": "wxj1234" -// }, -// "vault": { -// "store_in_vault": "ON_SUCCESS" -// }, -// "verification": { -// "method": "SCA_WHEN_REQUIRED" -// } -// } -// } -// }, -// "application_context": { -// "return_url": "return_url", -// "cancel_url": "return_url" -// } -//} - /// Describes request to confirm a payment source (approve an order) struct ConfirmPaymentSourceRequest: Encodable { @@ -174,5 +104,37 @@ struct ConfirmPaymentSourceRequest: Encodable { var verification = attributes.nestedContainer(keyedBy: VerificationKeys.self, forKey: .verification) try verification.encode(cardRequest.sca.rawValue, forKey: .method) //TODO } - } + +//{ +// "payment_source": { +// "card": { +// "number": "4111111111111111", +// "expiry": "2020-02", +// "name": "John Doe", +// "billing_address": { +// "address_line_1": "2211 N First Street", +// "address_line_2": "Building 17", +// "admin_area_2": "San Jose", +// "admin_area_1": "CA", +// "postal_code": "95131", +// "country_code": "US" +// }, +// "attributes": { +// "customer": { +// "id": "wxj1234" +// }, +// "vault": { +// "store_in_vault": "ON_SUCCESS" +// }, +// "verification": { +// "method": "SCA_WHEN_REQUIRED" +// } +// } +// } +// }, +// "application_context": { +// "return_url": "return_url", +// "cancel_url": "return_url" +// } +//} diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index 6dde9b843..15b88b3e2 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -70,19 +70,3 @@ public class APIClient { return url } } - -public struct RESTRequest { - public var path: String - public var method: HTTPMethod - public var headers: [HTTPHeader: String] - public var queryParameters: [String: String]? - public var body: Data? - - public init(path: String, method: HTTPMethod, headers: [HTTPHeader : String], queryParameters: [String : String]? = nil, body: Data? = nil) { - self.path = path - self.method = method - self.headers = headers - self.queryParameters = queryParameters - self.body = body - } -} diff --git a/Sources/CorePayments/Networking/AnalyticsService.swift b/Sources/CorePayments/Networking/AnalyticsService.swift index 677cfc44e..6cc4e1ef9 100644 --- a/Sources/CorePayments/Networking/AnalyticsService.swift +++ b/Sources/CorePayments/Networking/AnalyticsService.swift @@ -6,23 +6,23 @@ public struct AnalyticsService { // MARK: - Internal Properties private let coreConfig: CoreConfig - private let apiClient: APIClient + private let trackingEventsAPI: TrackingEventsAPI private let orderID: String // MARK: - Initializer public init(coreConfig: CoreConfig, orderID: String) { self.coreConfig = coreConfig - self.apiClient = APIClient(coreConfig: CoreConfig(clientID: coreConfig.clientID, environment: .live)) + self.trackingEventsAPI = TrackingEventsAPI() self.orderID = orderID } // MARK: - Internal Initializer /// Exposed for testing - init(coreConfig: CoreConfig, orderID: String, apiClient: APIClient) { + init(coreConfig: CoreConfig, orderID: String, trackingEventsAPI: TrackingEventsAPI) { self.coreConfig = coreConfig - self.apiClient = apiClient + self.trackingEventsAPI = trackingEventsAPI self.orderID = orderID } @@ -51,32 +51,9 @@ public struct AnalyticsService { orderID: orderID ) - let (_) = try await TrackingEventsAPI().sendEvent(with: eventData) + let (_) = try await trackingEventsAPI.sendEvent(with: eventData) } catch { NSLog("[PayPal SDK] Failed to send analytics: %@", error.localizedDescription) } } } - -class TrackingEventsAPI { - - func sendEvent(with analyticsEventData: AnalyticsEventData) async throws -> HTTPResponse { - let apiClient = APIClient(coreConfig: CoreConfig(clientID: analyticsEventData.clientID, environment: .live)) - - // encode the body -- todo move - let encoder = JSONEncoder() - encoder.keyEncodingStrategy = .convertToSnakeCase - let body = try encoder.encode(analyticsEventData) // handle with special - - let restRequest = RESTRequest( - path: "v1/tracking/events", - method: .post, - headers: [.contentType: "application/json"], - queryParameters: nil, - body: body - ) - - return try await apiClient.fetch(request: restRequest) - // skip HTTP parsing! - } -} diff --git a/Sources/CorePayments/Networking/RESTRequest.swift b/Sources/CorePayments/Networking/RESTRequest.swift new file mode 100644 index 000000000..8af37b5c3 --- /dev/null +++ b/Sources/CorePayments/Networking/RESTRequest.swift @@ -0,0 +1,24 @@ +import Foundation + +public struct RESTRequest { + + var path: String + var method: HTTPMethod + var headers: [HTTPHeader: String] + var queryParameters: [String: String]? + var body: Data? + + public init( + path: String, + method: HTTPMethod, + headers: [HTTPHeader : String], + queryParameters: [String : String]? = nil, + body: Data? = nil + ) { + self.path = path + self.method = method + self.headers = headers + self.queryParameters = queryParameters + self.body = body + } +} diff --git a/Sources/CorePayments/Networking/TrackingEventsAPI.swift b/Sources/CorePayments/Networking/TrackingEventsAPI.swift new file mode 100644 index 000000000..b8dc6fcc4 --- /dev/null +++ b/Sources/CorePayments/Networking/TrackingEventsAPI.swift @@ -0,0 +1,24 @@ +import Foundation + +class TrackingEventsAPI { + + func sendEvent(with analyticsEventData: AnalyticsEventData) async throws -> HTTPResponse { + let apiClient = APIClient(coreConfig: CoreConfig(clientID: analyticsEventData.clientID, environment: .live)) + + // encode the body -- todo move + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + let body = try encoder.encode(analyticsEventData) // handle with special + + let restRequest = RESTRequest( + path: "v1/tracking/events", + method: .post, + headers: [.contentType: "application/json"], + queryParameters: nil, + body: body + ) + + return try await apiClient.fetch(request: restRequest) + // skip HTTP parsing! + } +} From 93f3ba4b39f9b55f19100cf3f73dbf4871f5a6bd Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Wed, 9 Aug 2023 00:11:59 -0500 Subject: [PATCH 04/30] Remove AnalyticsEventRequest APIRequest protocol conformance --- PayPal.xcodeproj/project.pbxproj | 4 ---- .../CorePayments/AnalyticsEventRequest.swift | 23 ------------------- 2 files changed, 27 deletions(-) delete mode 100644 Sources/CorePayments/AnalyticsEventRequest.swift diff --git a/PayPal.xcodeproj/project.pbxproj b/PayPal.xcodeproj/project.pbxproj index c00c5d25e..7fb88af8e 100644 --- a/PayPal.xcodeproj/project.pbxproj +++ b/PayPal.xcodeproj/project.pbxproj @@ -38,7 +38,6 @@ 8071AFA729C8BEF3008A39E9 /* PayPalNativeShippingAddress_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8071AFA629C8BEF3008A39E9 /* PayPalNativeShippingAddress_Tests.swift */; }; 807BF58F2A2A5D19002F32B3 /* HTTPResponseParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807BF58E2A2A5D19002F32B3 /* HTTPResponseParser.swift */; }; 807BF5912A2A5D48002F32B3 /* HTTPResponseParser_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807BF5902A2A5D48002F32B3 /* HTTPResponseParser_Tests.swift */; }; - 807C5E67291027D400ECECD8 /* AnalyticsEventRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807C5E66291027D400ECECD8 /* AnalyticsEventRequest.swift */; }; 807C5E6929102D9800ECECD8 /* AnalyticsEventData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807C5E6829102D9800ECECD8 /* AnalyticsEventData.swift */; }; 808EEA81291321FE001B6765 /* AnalyticsEventRequest_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 808EEA80291321FE001B6765 /* AnalyticsEventRequest_Tests.swift */; }; 80D0C1382731CC9B00548A3D /* PayPalNativeCheckoutClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE7116152723227200165069 /* PayPalNativeCheckoutClient.swift */; }; @@ -230,7 +229,6 @@ 8071AFA629C8BEF3008A39E9 /* PayPalNativeShippingAddress_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalNativeShippingAddress_Tests.swift; sourceTree = ""; }; 807BF58E2A2A5D19002F32B3 /* HTTPResponseParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPResponseParser.swift; sourceTree = ""; }; 807BF5902A2A5D48002F32B3 /* HTTPResponseParser_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPResponseParser_Tests.swift; sourceTree = ""; }; - 807C5E66291027D400ECECD8 /* AnalyticsEventRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsEventRequest.swift; sourceTree = ""; }; 807C5E6829102D9800ECECD8 /* AnalyticsEventData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsEventData.swift; sourceTree = ""; }; 808EEA80291321FE001B6765 /* AnalyticsEventRequest_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsEventRequest_Tests.swift; sourceTree = ""; }; 80B9F85126B8750000D67843 /* CorePayments.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CorePayments.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -522,7 +520,6 @@ 06CE009826F3D1660000CC46 /* CoreConfig.swift */, 065A4DBB26FCD8080007014A /* CoreSDKError.swift */, BEA100E526EF9EDA0036A6A5 /* Networking */, - 807C5E66291027D400ECECD8 /* AnalyticsEventRequest.swift */, 807C5E6829102D9800ECECD8 /* AnalyticsEventData.swift */, 8021B68F29144E6D000FBC54 /* PayPalCoreConstants.swift */, 80DB2F752980795D00CFB86A /* CorePaymentsError.swift */, @@ -1286,7 +1283,6 @@ E6022E7A2857C6BE008B0E27 /* SupportedCountryCurrencyType.swift in Sources */, E6022E7B2857C6BE008B0E27 /* SupportedPaymentMethodsType.swift in Sources */, E6022E7C2857C6BE008B0E27 /* FundingEligibilityResponse.swift in Sources */, - 807C5E67291027D400ECECD8 /* AnalyticsEventRequest.swift in Sources */, E6022E7F2857C6BE008B0E27 /* FundingEligibilityQuery.swift in Sources */, E6022E802857C6BE008B0E27 /* GraphQLQueryResponse.swift in Sources */, 80E643832A1EBBD2008FD705 /* HTTPResponse.swift in Sources */, diff --git a/Sources/CorePayments/AnalyticsEventRequest.swift b/Sources/CorePayments/AnalyticsEventRequest.swift deleted file mode 100644 index 92db849bd..000000000 --- a/Sources/CorePayments/AnalyticsEventRequest.swift +++ /dev/null @@ -1,23 +0,0 @@ -import Foundation - -struct AnalyticsEventRequest: APIRequest { - - init(eventData: AnalyticsEventData) throws { - let encoder = JSONEncoder() - encoder.keyEncodingStrategy = .convertToSnakeCase - body = try encoder.encode(eventData) - } - - // MARK: - APIRequest conformance - - typealias ResponseType = EmptyResponse - - var path = "v1/tracking/events" - var method: HTTPMethod = .post - var headers: [HTTPHeader: String] = [.contentType: "application/json"] - var body: Data? - - // api.sandbox.paypal.com does not currently send FPTI events to BigQuery/Looker -} - -struct EmptyResponse: Decodable { } From a51ab8bb36474b7a7de2ddd5655efe5bb8d9bb7d Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Wed, 9 Aug 2023 00:13:28 -0500 Subject: [PATCH 05/30] Add TODO for adding graphQL func --- Sources/CorePayments/Networking/APIClient.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index 15b88b3e2..cd44facbe 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -55,6 +55,9 @@ public class APIClient { return try await http.performRequest(httpRequest) } + // TODO: - Add GraphQL func + // public func fetch(request: GraphQLRequest) async throws -> HTTPResponse { } + private func constructURL(path: String, queryParameters: [String: String]) throws -> URL { let urlString = coreConfig.environment.baseURL.appendingPathComponent(path) var urlComponents = URLComponents(url: urlString, resolvingAgainstBaseURL: false) From e5285f7687797b4e22d718d7aae6a411e1a345c6 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Wed, 9 Aug 2023 00:26:12 -0500 Subject: [PATCH 06/30] Cleanup methods to remove & add TODOs for easier review --- .../APIRequests/CheckoutOrdersAPI.swift | 12 +++-- .../ConfirmPaymentSourceRequest.swift | 48 +++---------------- .../CorePayments/Networking/APIClient.swift | 22 ++------- Sources/CorePayments/Networking/HTTP.swift | 15 +----- .../Networking/HTTPResponse.swift | 1 + .../Networking/HTTPResponseParser.swift | 1 + .../Networking/Protocols/APIRequest.swift | 1 + .../Networking/TrackingEventsAPI.swift | 8 ++-- 8 files changed, 30 insertions(+), 78 deletions(-) diff --git a/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift b/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift index b0492152b..7e714eab5 100644 --- a/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift +++ b/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift @@ -5,11 +5,17 @@ import CorePayments class CheckoutOrdersAPI { - let coreConfig: CoreConfig + // MARK: - Private Propertires + + private let coreConfig: CoreConfig + + // MARK: - Initializer init(coreConfig: CoreConfig) { self.coreConfig = coreConfig } + + // MARK: - Internal Functions func confirmPaymentSource(clientID: String, cardRequest: CardRequest) async throws -> ConfirmPaymentSourceResponse { let apiClient = APIClient(coreConfig: coreConfig) @@ -18,10 +24,10 @@ class CheckoutOrdersAPI { let base64EncodedCredentials = Data(clientID.appending(":").utf8).base64EncodedString() - // encode the body -- todo move + // TODO: - Move JSON encoding into custom class, similar to HTTPResponseParser let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase - let body = try encoder.encode(confirmData) // handle with special + let body = try encoder.encode(confirmData) let restRequest = RESTRequest( path: "/v2/checkout/orders/\(cardRequest.orderID)/confirm-payment-source", diff --git a/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift b/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift index 1b1f92847..833858da1 100644 --- a/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift +++ b/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift @@ -57,12 +57,11 @@ struct ConfirmPaymentSourceRequest: Encodable { case method } - // MARK: - Properties + // MARK: - Private Properties - let returnURL = PayPalCoreConstants.callbackURLScheme + "://card/success" - let cancelURL = PayPalCoreConstants.callbackURLScheme + "://card/cancel" - - let cardRequest: CardRequest + private let returnURL = PayPalCoreConstants.callbackURLScheme + "://card/success" + private let cancelURL = PayPalCoreConstants.callbackURLScheme + "://card/cancel" + private let cardRequest: CardRequest // MARK: - Initializer @@ -96,45 +95,12 @@ struct ConfirmPaymentSourceRequest: Encodable { } var attributes = card.nestedContainer(keyedBy: AttributesKeys.self, forKey: .attributes) var customer = attributes.nestedContainer(keyedBy: CustomerKeys.self, forKey: .customer) - try customer.encode("fake-customer-id", forKey: .id) // TODO + try customer.encode("fake-customer-id", forKey: .id) // TODO: - Re-expose vault feature var vault = attributes.nestedContainer(keyedBy: VaultKeys.self, forKey: .vault) - try vault.encode("ON_SUCCESS", forKey: .storeInVault) // TODO + try vault.encode("ON_SUCCESS", forKey: .storeInVault) // TODO: - Re-expose vault feature var verification = attributes.nestedContainer(keyedBy: VerificationKeys.self, forKey: .verification) - try verification.encode(cardRequest.sca.rawValue, forKey: .method) //TODO + try verification.encode(cardRequest.sca.rawValue, forKey: .method) } } - -//{ -// "payment_source": { -// "card": { -// "number": "4111111111111111", -// "expiry": "2020-02", -// "name": "John Doe", -// "billing_address": { -// "address_line_1": "2211 N First Street", -// "address_line_2": "Building 17", -// "admin_area_2": "San Jose", -// "admin_area_1": "CA", -// "postal_code": "95131", -// "country_code": "US" -// }, -// "attributes": { -// "customer": { -// "id": "wxj1234" -// }, -// "vault": { -// "store_in_vault": "ON_SUCCESS" -// }, -// "verification": { -// "method": "SCA_WHEN_REQUIRED" -// } -// } -// } -// }, -// "application_context": { -// "return_url": "return_url", -// "cancel_url": "return_url" -// } -//} diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index cd44facbe..67dd77c3a 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -27,21 +27,7 @@ public class APIClient { // MARK: - Public Methods - /// :nodoc: This method is exposed for internal PayPal use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time. - public func fetch(request: T) async throws -> (T.ResponseType) { - let url = try constructURL(path: request.path, queryParameters: request.queryParameters) - - let httpRequest = HTTPRequest( - url: url, - method: request.method, - body: request.body, - headers: request.headers - ) - - let httpResponse = try await http.performRequest(httpRequest) - return try HTTPResponseParser().parse(httpResponse, as: T.ResponseType.self) - } - + /// :nodoc: public func fetch(request: RESTRequest) async throws -> HTTPResponse { let url = try constructURL(path: request.path, queryParameters: request.queryParameters ?? [:]) // cleaner way @@ -55,9 +41,11 @@ public class APIClient { return try await http.performRequest(httpRequest) } - // TODO: - Add GraphQL func + // TODO: - Add GraphQL equivalent request type & function // public func fetch(request: GraphQLRequest) async throws -> HTTPResponse { } + // MARK: - Private Methods + private func constructURL(path: String, queryParameters: [String: String]) throws -> URL { let urlString = coreConfig.environment.baseURL.appendingPathComponent(path) var urlComponents = URLComponents(url: urlString, resolvingAgainstBaseURL: false) @@ -67,7 +55,7 @@ public class APIClient { } guard let url = urlComponents?.url else { - throw CorePaymentsError.clientIDNotFoundError // fix + throw CorePaymentsError.clientIDNotFoundError // TODO: - throw proper error type } return url diff --git a/Sources/CorePayments/Networking/HTTP.swift b/Sources/CorePayments/Networking/HTTP.swift index 1131c8355..a27443d99 100644 --- a/Sources/CorePayments/Networking/HTTP.swift +++ b/Sources/CorePayments/Networking/HTTP.swift @@ -1,6 +1,6 @@ import Foundation -/// `HTTP` interfaces directly with `URLSession` to execute network requests. +/// `HTTP` constructs `URLRequest`s and interfaces directly with `URLSession` to execute network requests. class HTTP { let coreConfig: CoreConfig @@ -14,19 +14,6 @@ class HTTP { self.coreConfig = coreConfig } -// func performRequest(_ request: any APIRequest) async throws -> HTTPResponse { -// guard let urlRequest = request.toURLRequest(environment: coreConfig.environment) else { -// throw APIClientError.invalidURLRequestError -// } -// -// let (data, response) = try await urlSession.performRequest(with: urlRequest) -// guard let response = response as? HTTPURLResponse else { -// throw APIClientError.invalidURLResponseError -// } -// -// return HTTPResponse(status: response.statusCode, body: data) -// } - func performRequest(_ httpRequest: HTTPRequest) async throws -> HTTPResponse { var urlRequest = URLRequest(url: httpRequest.url) urlRequest.httpMethod = httpRequest.method.rawValue diff --git a/Sources/CorePayments/Networking/HTTPResponse.swift b/Sources/CorePayments/Networking/HTTPResponse.swift index f01a9674d..4e81d07f3 100644 --- a/Sources/CorePayments/Networking/HTTPResponse.swift +++ b/Sources/CorePayments/Networking/HTTPResponse.swift @@ -1,5 +1,6 @@ import Foundation +/// :nodoc: public struct HTTPResponse { let status: Int diff --git a/Sources/CorePayments/Networking/HTTPResponseParser.swift b/Sources/CorePayments/Networking/HTTPResponseParser.swift index 0b7a0bbf5..9517c89f2 100644 --- a/Sources/CorePayments/Networking/HTTPResponseParser.swift +++ b/Sources/CorePayments/Networking/HTTPResponseParser.swift @@ -1,5 +1,6 @@ import Foundation +/// :nodoc: public class HTTPResponseParser { private let decoder: JSONDecoder diff --git a/Sources/CorePayments/Networking/Protocols/APIRequest.swift b/Sources/CorePayments/Networking/Protocols/APIRequest.swift index 24e318c58..a6c281843 100644 --- a/Sources/CorePayments/Networking/Protocols/APIRequest.swift +++ b/Sources/CorePayments/Networking/Protocols/APIRequest.swift @@ -1,5 +1,6 @@ import Foundation +// TODO: - Remove this protocol once tests are updated 🎉 public protocol APIRequest { associatedtype ResponseType: Decodable diff --git a/Sources/CorePayments/Networking/TrackingEventsAPI.swift b/Sources/CorePayments/Networking/TrackingEventsAPI.swift index b8dc6fcc4..23176f04c 100644 --- a/Sources/CorePayments/Networking/TrackingEventsAPI.swift +++ b/Sources/CorePayments/Networking/TrackingEventsAPI.swift @@ -1,14 +1,17 @@ import Foundation class TrackingEventsAPI { + + // MARK: - Internal Functions func sendEvent(with analyticsEventData: AnalyticsEventData) async throws -> HTTPResponse { + // api.sandbox.paypal.com does not currently send FPTI events to BigQuery/Looker let apiClient = APIClient(coreConfig: CoreConfig(clientID: analyticsEventData.clientID, environment: .live)) - // encode the body -- todo move + // TODO: - Move JSON encoding into custom class, similar to HTTPResponseParser let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase - let body = try encoder.encode(analyticsEventData) // handle with special + let body = try encoder.encode(analyticsEventData) let restRequest = RESTRequest( path: "v1/tracking/events", @@ -19,6 +22,5 @@ class TrackingEventsAPI { ) return try await apiClient.fetch(request: restRequest) - // skip HTTP parsing! } } From 8e07e5d917c8423a9c490541e1ba315236892a01 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Wed, 9 Aug 2023 10:18:27 -0500 Subject: [PATCH 07/30] Fix linter warnings --- Sources/CorePayments/Networking/APIClient.swift | 2 +- Sources/CorePayments/Networking/RESTRequest.swift | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index 67dd77c3a..93d185814 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -27,7 +27,7 @@ public class APIClient { // MARK: - Public Methods - /// :nodoc: + /// :nodoc: public func fetch(request: RESTRequest) async throws -> HTTPResponse { let url = try constructURL(path: request.path, queryParameters: request.queryParameters ?? [:]) // cleaner way diff --git a/Sources/CorePayments/Networking/RESTRequest.swift b/Sources/CorePayments/Networking/RESTRequest.swift index 8af37b5c3..9c3a2232d 100644 --- a/Sources/CorePayments/Networking/RESTRequest.swift +++ b/Sources/CorePayments/Networking/RESTRequest.swift @@ -1,5 +1,6 @@ import Foundation +/// :nodoc: public struct RESTRequest { var path: String @@ -11,8 +12,8 @@ public struct RESTRequest { public init( path: String, method: HTTPMethod, - headers: [HTTPHeader : String], - queryParameters: [String : String]? = nil, + headers: [HTTPHeader: String], + queryParameters: [String: String]? = nil, body: Data? = nil ) { self.path = path From c83eb48af851dbb8133422031ec644e766262c7b Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Wed, 9 Aug 2023 15:56:55 -0500 Subject: [PATCH 08/30] Remove params from ConfirmPaymentSourceRequest previously used in Vault w/ purchase flow --- .../ConfirmPaymentSourceRequest.swift | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift b/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift index 833858da1..f3a6749a1 100644 --- a/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift +++ b/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift @@ -40,19 +40,10 @@ struct ConfirmPaymentSourceRequest: Encodable { } enum AttributesKeys: String, CodingKey { - case customer case vault case verification } - enum CustomerKeys: String, CodingKey { - case id - } - - enum VaultKeys: String, CodingKey { - case storeInVault - } - enum VerificationKeys: String, CodingKey { case method } @@ -93,13 +84,8 @@ struct ConfirmPaymentSourceRequest: Encodable { try billingAddress.encode(cardBillingInfo.locality, forKey: .adminArea2) try billingAddress.encode(cardBillingInfo.countryCode, forKey: .countryCode) } - var attributes = card.nestedContainer(keyedBy: AttributesKeys.self, forKey: .attributes) - var customer = attributes.nestedContainer(keyedBy: CustomerKeys.self, forKey: .customer) - try customer.encode("fake-customer-id", forKey: .id) // TODO: - Re-expose vault feature - - var vault = attributes.nestedContainer(keyedBy: VaultKeys.self, forKey: .vault) - try vault.encode("ON_SUCCESS", forKey: .storeInVault) // TODO: - Re-expose vault feature + var attributes = card.nestedContainer(keyedBy: AttributesKeys.self, forKey: .attributes) var verification = attributes.nestedContainer(keyedBy: VerificationKeys.self, forKey: .verification) try verification.encode(cardRequest.sca.rawValue, forKey: .method) } From 55f71c43228d0e9adae8cc9d59f56237081e59b1 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Wed, 9 Aug 2023 16:00:14 -0500 Subject: [PATCH 09/30] Cleanup - make ConfirmPaymentSource CodingKeys private --- .../APIRequests/CheckoutOrdersAPI.swift | 2 +- .../APIRequests/ConfirmPaymentSourceRequest.swift | 14 +++++++------- Sources/CorePayments/Networking/HTTP.swift | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift b/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift index 7e714eab5..8e34f58f6 100644 --- a/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift +++ b/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift @@ -15,7 +15,7 @@ class CheckoutOrdersAPI { self.coreConfig = coreConfig } - // MARK: - Internal Functions + // MARK: - Internal Methods func confirmPaymentSource(clientID: String, cardRequest: CardRequest) async throws -> ConfirmPaymentSourceResponse { let apiClient = APIClient(coreConfig: coreConfig) diff --git a/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift b/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift index f3a6749a1..6a44efde3 100644 --- a/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift +++ b/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift @@ -8,21 +8,21 @@ struct ConfirmPaymentSourceRequest: Encodable { // MARK: - Coding Keys - enum TopLevelKeys: String, CodingKey { + private enum TopLevelKeys: String, CodingKey { case paymentSource = "payment_source" case applicationContext = "application_context" } - enum ApplicationContextKeys: String, CodingKey { + private enum ApplicationContextKeys: String, CodingKey { case returnURL = "return_url" case cancelURL = "cancel_url" } - enum PaymentSourceKeys: String, CodingKey { + private enum PaymentSourceKeys: String, CodingKey { case card } - enum CardKeys: String, CodingKey { + private enum CardKeys: String, CodingKey { case number case expiry case billingAddress @@ -30,7 +30,7 @@ struct ConfirmPaymentSourceRequest: Encodable { case attributes } - enum BillingAddressKeys: String, CodingKey { + private enum BillingAddressKeys: String, CodingKey { case addressLine1 = "address_line_1" case addressLine2 = "address_line_2" case adminArea1 = "admin_area_1" @@ -39,12 +39,12 @@ struct ConfirmPaymentSourceRequest: Encodable { case countryCode } - enum AttributesKeys: String, CodingKey { + private enum AttributesKeys: String, CodingKey { case vault case verification } - enum VerificationKeys: String, CodingKey { + private enum VerificationKeys: String, CodingKey { case method } diff --git a/Sources/CorePayments/Networking/HTTP.swift b/Sources/CorePayments/Networking/HTTP.swift index a27443d99..d37c1a4ee 100644 --- a/Sources/CorePayments/Networking/HTTP.swift +++ b/Sources/CorePayments/Networking/HTTP.swift @@ -32,6 +32,7 @@ class HTTP { } } +/// :nodoc: public struct HTTPRequest { let url: URL From 1243524742182defa77b52dfb07107248729346c Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Wed, 9 Aug 2023 16:11:57 -0500 Subject: [PATCH 10/30] A dd docstrings with specifics on how to find each API in PPaaS --- Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift | 3 +++ Sources/CorePayments/Networking/TrackingEventsAPI.swift | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift b/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift index 8e34f58f6..066eea4e3 100644 --- a/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift +++ b/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift @@ -3,6 +3,9 @@ import Foundation import CorePayments #endif +/// This class coordinates networking logic for communicating with the v2/checkout/orders API. +/// +/// Details on this PayPal API can be found in PPaaS under Merchant > Checkout > Orders > v2. class CheckoutOrdersAPI { // MARK: - Private Propertires diff --git a/Sources/CorePayments/Networking/TrackingEventsAPI.swift b/Sources/CorePayments/Networking/TrackingEventsAPI.swift index 23176f04c..58a8458ce 100644 --- a/Sources/CorePayments/Networking/TrackingEventsAPI.swift +++ b/Sources/CorePayments/Networking/TrackingEventsAPI.swift @@ -1,5 +1,8 @@ import Foundation +/// This class coordinates networking logic for communicating with the v1/tracking/events API. +/// +/// Details on this PayPal API can be found in PPaaS under Infrastructure > Experimentation > Tracking Events > v1. class TrackingEventsAPI { // MARK: - Internal Functions From 63cc3f605dffe52b3936a36907914586f6cdf556 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Wed, 9 Aug 2023 16:57:56 -0500 Subject: [PATCH 11/30] Move HTTPRequest into own swift file --- PayPal.xcodeproj/project.pbxproj | 6 +++++- Sources/CorePayments/Networking/HTTP.swift | 9 --------- Sources/CorePayments/Networking/HTTPRequest.swift | 10 ++++++++++ 3 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 Sources/CorePayments/Networking/HTTPRequest.swift diff --git a/PayPal.xcodeproj/project.pbxproj b/PayPal.xcodeproj/project.pbxproj index 7fb88af8e..6a290e28d 100644 --- a/PayPal.xcodeproj/project.pbxproj +++ b/PayPal.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -48,6 +48,7 @@ 80DBC9DE29C3B57200462539 /* PayPalNativeShippingAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DBC9DD29C3B57200462539 /* PayPalNativeShippingAddress.swift */; }; 80DBC9E029C3B8A800462539 /* PayPalNativeShippingMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DBC9DF29C3B8A800462539 /* PayPalNativeShippingMethod.swift */; }; 80DCC59E2719DB6F00EC7C5A /* CardClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DCC59D2719DB6F00EC7C5A /* CardClientError.swift */; }; + 80E237DF2A84434B00FF18CA /* HTTPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E237DE2A84434B00FF18CA /* HTTPRequest.swift */; }; 80E2FDBE2A83528B0045593D /* CheckoutOrdersAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E2FDBD2A83528B0045593D /* CheckoutOrdersAPI.swift */; }; 80E2FDC12A83535A0045593D /* TrackingEventsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E2FDBF2A8353550045593D /* TrackingEventsAPI.swift */; }; 80E2FDC32A8354AD0045593D /* RESTRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E2FDC22A8354AD0045593D /* RESTRequest.swift */; }; @@ -239,6 +240,7 @@ 80DBC9DD29C3B57200462539 /* PayPalNativeShippingAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalNativeShippingAddress.swift; sourceTree = ""; }; 80DBC9DF29C3B8A800462539 /* PayPalNativeShippingMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalNativeShippingMethod.swift; sourceTree = ""; }; 80DCC59D2719DB6F00EC7C5A /* CardClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardClientError.swift; sourceTree = ""; }; + 80E237DE2A84434B00FF18CA /* HTTPRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPRequest.swift; sourceTree = ""; }; 80E2FDBD2A83528B0045593D /* CheckoutOrdersAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckoutOrdersAPI.swift; sourceTree = ""; }; 80E2FDBF2A8353550045593D /* TrackingEventsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingEventsAPI.swift; sourceTree = ""; }; 80E2FDC22A8354AD0045593D /* RESTRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRequest.swift; sourceTree = ""; }; @@ -697,6 +699,7 @@ 804E628529380B04004B9FEF /* AnalyticsService.swift */, 80E2FDBF2A8353550045593D /* TrackingEventsAPI.swift */, 804E62812937EBCE004B9FEF /* HTTP.swift */, + 80E237DE2A84434B00FF18CA /* HTTPRequest.swift */, 80E643822A1EBBD2008FD705 /* HTTPResponse.swift */, 80E2FDC22A8354AD0045593D /* RESTRequest.swift */, 807BF58E2A2A5D19002F32B3 /* HTTPResponseParser.swift */, @@ -1289,6 +1292,7 @@ 807C5E6929102D9800ECECD8 /* AnalyticsEventData.swift in Sources */, E6022E822857C6BE008B0E27 /* GraphQLError.swift in Sources */, E6022E832857C6BE008B0E27 /* GraphQLQuery.swift in Sources */, + 80E237DF2A84434B00FF18CA /* HTTPRequest.swift in Sources */, E6022E842857C6BE008B0E27 /* GraphQLClient.swift in Sources */, 8021B69029144E6D000FBC54 /* PayPalCoreConstants.swift in Sources */, 80DB2F762980795D00CFB86A /* CorePaymentsError.swift in Sources */, diff --git a/Sources/CorePayments/Networking/HTTP.swift b/Sources/CorePayments/Networking/HTTP.swift index d37c1a4ee..50a4ab351 100644 --- a/Sources/CorePayments/Networking/HTTP.swift +++ b/Sources/CorePayments/Networking/HTTP.swift @@ -31,12 +31,3 @@ class HTTP { return HTTPResponse(status: response.statusCode, body: data) } } - -/// :nodoc: -public struct HTTPRequest { - - let url: URL - let method: HTTPMethod - let body: Data? - let headers: [HTTPHeader: String] -} diff --git a/Sources/CorePayments/Networking/HTTPRequest.swift b/Sources/CorePayments/Networking/HTTPRequest.swift new file mode 100644 index 000000000..25f185dfd --- /dev/null +++ b/Sources/CorePayments/Networking/HTTPRequest.swift @@ -0,0 +1,10 @@ +import Foundation + +/// :nodoc: +public struct HTTPRequest { + + let url: URL + let method: HTTPMethod + let body: Data? + let headers: [HTTPHeader: String] +} From 00107331565548097c64294c6e8809536f0d6fab Mon Sep 17 00:00:00 2001 From: scannillo <35243507+scannillo@users.noreply.github.com> Date: Thu, 10 Aug 2023 09:57:15 -0500 Subject: [PATCH 12/30] Remove APIRequest conformance from ClientIDRequest in Demo (#178) --- Demo/Demo/Models/ClientIDRequest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Demo/Demo/Models/ClientIDRequest.swift b/Demo/Demo/Models/ClientIDRequest.swift index 265ea80f4..08a726a84 100644 --- a/Demo/Demo/Models/ClientIDRequest.swift +++ b/Demo/Demo/Models/ClientIDRequest.swift @@ -1,7 +1,7 @@ import Foundation import CorePayments -struct ClientIDRequest: APIRequest { +struct ClientIDRequest { typealias ResponseType = ClientIDResponse From 04938411ce0f51beab1c1cac761fb96ee1f8df0f Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Thu, 10 Aug 2023 10:12:43 -0500 Subject: [PATCH 13/30] WIP - begin adding GraphQL template struct & method to APIClient --- .../CorePayments/Networking/APIClient.swift | 93 ++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index 93d185814..9923d63d0 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -42,7 +42,42 @@ public class APIClient { } // TODO: - Add GraphQL equivalent request type & function - // public func fetch(request: GraphQLRequest) async throws -> HTTPResponse { } +// public func fetch(request: GraphQLRequest) async throws -> HTTPResponse { } + public func fetch() throws { + let queryString = """ + mutation UpdateVaultSetupToken( + $clientID: String!, + $vaultSetupToken: String!, + $paymentSource: PaymentSource + ) { + updateVaultSetupToken( + clientId: $clientID + vaultSetupToken: $vaultSetupToken + paymentSource: $paymentSource + ) { + id, + status, + links { + rel, href + } + } + } + """ + + // TODO: - Move JSON encoding into custom class, similar to HTTPResponseParser + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + let variables = try encoder.encode(VaultDataEncodableVariables()) + + // Combine varialbes & query into single post body + let graphQLRequest = GraphQLRequest( + queryNameForURL: "UpdateVaultSetupToken", + query: queryString, + variables: variables + ) + + // Construct HTTPRequest + } // MARK: - Private Methods @@ -61,3 +96,59 @@ public class APIClient { return url } } + +/// :nodoc: +public struct GraphQLRequest { + let queryNameForURL: String? + let query: String + let variables: Data // Dictionary +} + +struct VaultDataEncodableVariables: Encodable { + + // MARK: - Coding KEys + + private enum TopLevelKeys: String, CodingKey { + case clientID + case vaultSetupToken + case paymentSource + } + + private enum PaymentSourceKeys: String, CodingKey { + case card + } + + private enum CardKeys: String, CodingKey { + case number + case securityCode + case expiry + case name + } + + // MARK: - Initializer + + // TODO: - Migrate details from VaultRequest introduced in Victoria's PR + + // MARK: - Custom Encoder + + func encode(to encoder: Encoder) throws { + var topLevel = encoder.container(keyedBy: TopLevelKeys.self) + try topLevel.encode("fake-client-id", forKey: .clientID) + try topLevel.encode("fake-vault-setup-token", forKey: .vaultSetupToken) + + var paymentSource = topLevel.nestedContainer(keyedBy: PaymentSourceKeys.self, forKey: .paymentSource) + + var card = paymentSource.nestedContainer(keyedBy: CardKeys.self, forKey: .card) + try card.encode("4111 1111 1111 1111", forKey: .number) + try card.encode("123", forKey: .securityCode) + try card.encode("2027-09", forKey: .expiry) + try card.encode("Sammy", forKey: .name) + } +} + +// SAMPLE +//{ +// "query": "query GreetingQuery ($arg1: String) { hello (name: $arg1) { value } }", +// "operationName": "GreetingQuery", +// "variables": { "arg1": "Timothy" } +//} From b5289e3bf6dd8cf16cd40092267704228b978f85 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Wed, 9 Aug 2023 16:57:56 -0500 Subject: [PATCH 14/30] Move HTTPRequest into own swift file --- PayPal.xcodeproj/project.pbxproj | 6 ++++- Sources/CorePayments/Networking/HTTP.swift | 9 -------- .../CorePayments/Networking/HTTPRequest.swift | 23 +++++++++++++++++++ 3 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 Sources/CorePayments/Networking/HTTPRequest.swift diff --git a/PayPal.xcodeproj/project.pbxproj b/PayPal.xcodeproj/project.pbxproj index 7fb88af8e..6a290e28d 100644 --- a/PayPal.xcodeproj/project.pbxproj +++ b/PayPal.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -48,6 +48,7 @@ 80DBC9DE29C3B57200462539 /* PayPalNativeShippingAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DBC9DD29C3B57200462539 /* PayPalNativeShippingAddress.swift */; }; 80DBC9E029C3B8A800462539 /* PayPalNativeShippingMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DBC9DF29C3B8A800462539 /* PayPalNativeShippingMethod.swift */; }; 80DCC59E2719DB6F00EC7C5A /* CardClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DCC59D2719DB6F00EC7C5A /* CardClientError.swift */; }; + 80E237DF2A84434B00FF18CA /* HTTPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E237DE2A84434B00FF18CA /* HTTPRequest.swift */; }; 80E2FDBE2A83528B0045593D /* CheckoutOrdersAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E2FDBD2A83528B0045593D /* CheckoutOrdersAPI.swift */; }; 80E2FDC12A83535A0045593D /* TrackingEventsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E2FDBF2A8353550045593D /* TrackingEventsAPI.swift */; }; 80E2FDC32A8354AD0045593D /* RESTRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E2FDC22A8354AD0045593D /* RESTRequest.swift */; }; @@ -239,6 +240,7 @@ 80DBC9DD29C3B57200462539 /* PayPalNativeShippingAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalNativeShippingAddress.swift; sourceTree = ""; }; 80DBC9DF29C3B8A800462539 /* PayPalNativeShippingMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalNativeShippingMethod.swift; sourceTree = ""; }; 80DCC59D2719DB6F00EC7C5A /* CardClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardClientError.swift; sourceTree = ""; }; + 80E237DE2A84434B00FF18CA /* HTTPRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPRequest.swift; sourceTree = ""; }; 80E2FDBD2A83528B0045593D /* CheckoutOrdersAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckoutOrdersAPI.swift; sourceTree = ""; }; 80E2FDBF2A8353550045593D /* TrackingEventsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingEventsAPI.swift; sourceTree = ""; }; 80E2FDC22A8354AD0045593D /* RESTRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRequest.swift; sourceTree = ""; }; @@ -697,6 +699,7 @@ 804E628529380B04004B9FEF /* AnalyticsService.swift */, 80E2FDBF2A8353550045593D /* TrackingEventsAPI.swift */, 804E62812937EBCE004B9FEF /* HTTP.swift */, + 80E237DE2A84434B00FF18CA /* HTTPRequest.swift */, 80E643822A1EBBD2008FD705 /* HTTPResponse.swift */, 80E2FDC22A8354AD0045593D /* RESTRequest.swift */, 807BF58E2A2A5D19002F32B3 /* HTTPResponseParser.swift */, @@ -1289,6 +1292,7 @@ 807C5E6929102D9800ECECD8 /* AnalyticsEventData.swift in Sources */, E6022E822857C6BE008B0E27 /* GraphQLError.swift in Sources */, E6022E832857C6BE008B0E27 /* GraphQLQuery.swift in Sources */, + 80E237DF2A84434B00FF18CA /* HTTPRequest.swift in Sources */, E6022E842857C6BE008B0E27 /* GraphQLClient.swift in Sources */, 8021B69029144E6D000FBC54 /* PayPalCoreConstants.swift in Sources */, 80DB2F762980795D00CFB86A /* CorePaymentsError.swift in Sources */, diff --git a/Sources/CorePayments/Networking/HTTP.swift b/Sources/CorePayments/Networking/HTTP.swift index d37c1a4ee..50a4ab351 100644 --- a/Sources/CorePayments/Networking/HTTP.swift +++ b/Sources/CorePayments/Networking/HTTP.swift @@ -31,12 +31,3 @@ class HTTP { return HTTPResponse(status: response.statusCode, body: data) } } - -/// :nodoc: -public struct HTTPRequest { - - let url: URL - let method: HTTPMethod - let body: Data? - let headers: [HTTPHeader: String] -} diff --git a/Sources/CorePayments/Networking/HTTPRequest.swift b/Sources/CorePayments/Networking/HTTPRequest.swift new file mode 100644 index 000000000..53d7cf08e --- /dev/null +++ b/Sources/CorePayments/Networking/HTTPRequest.swift @@ -0,0 +1,23 @@ +import Foundation + +/// :nodoc: +public struct HTTPRequest { + + let url: URL + let method: HTTPMethod + let body: Data? + let headers: [HTTPHeader: String] + + /// :nodoc: + public init( + url: URL, + method: HTTPMethod, + body: Data?, + headers: [HTTPHeader: String] + ) { + self.url = url + self.method = method + self.body = body + self.headers = headers + } +} From bc2213bc5e842825037ba0a83ff32dc597c626a3 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Thu, 10 Aug 2023 13:26:38 -0500 Subject: [PATCH 15/30] PR Feedback - use encodeIfPresent for optional properties, versus sending in POST body --- .../APIRequests/ConfirmPaymentSourceRequest.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift b/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift index 6a44efde3..16b96ee0a 100644 --- a/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift +++ b/Sources/CardPayments/APIRequests/ConfirmPaymentSourceRequest.swift @@ -73,16 +73,16 @@ struct ConfirmPaymentSourceRequest: Encodable { var card = paymentSource.nestedContainer(keyedBy: CardKeys.self, forKey: .card) try card.encode(cardRequest.card.number, forKey: .number) try card.encode("\(cardRequest.card.expirationYear)-\(cardRequest.card.expirationMonth)", forKey: .expiry) - try card.encode(cardRequest.card.cardholderName, forKey: .name) + try card.encodeIfPresent(cardRequest.card.cardholderName, forKey: .name) if let cardBillingInfo = cardRequest.card.billingAddress { var billingAddress = card.nestedContainer(keyedBy: BillingAddressKeys.self, forKey: .billingAddress) - try billingAddress.encode(cardBillingInfo.addressLine1, forKey: .addressLine1) - try billingAddress.encode(cardBillingInfo.addressLine2, forKey: .addressLine2) - try billingAddress.encode(cardBillingInfo.postalCode, forKey: .postalCode) - try billingAddress.encode(cardBillingInfo.region, forKey: .adminArea1) - try billingAddress.encode(cardBillingInfo.locality, forKey: .adminArea2) - try billingAddress.encode(cardBillingInfo.countryCode, forKey: .countryCode) + try billingAddress.encodeIfPresent(cardBillingInfo.addressLine1, forKey: .addressLine1) + try billingAddress.encodeIfPresent(cardBillingInfo.addressLine2, forKey: .addressLine2) + try billingAddress.encodeIfPresent(cardBillingInfo.postalCode, forKey: .postalCode) + try billingAddress.encodeIfPresent(cardBillingInfo.region, forKey: .adminArea1) + try billingAddress.encodeIfPresent(cardBillingInfo.locality, forKey: .adminArea2) + try billingAddress.encodeIfPresent(cardBillingInfo.countryCode, forKey: .countryCode) } var attributes = card.nestedContainer(keyedBy: AttributesKeys.self, forKey: .attributes) From 4a43375b8aca06d35ac40efda2af993e95fc8f6f Mon Sep 17 00:00:00 2001 From: Victoria Park Date: Thu, 10 Aug 2023 12:28:44 -0700 Subject: [PATCH 16/30] Remove Eligibility feature (#180) * Remove Eligibility feature * remove two types for Eligibility feature --- PayPal.xcodeproj/project.pbxproj | 36 ----- Sources/CorePayments/Eligibility.swift | 11 -- Sources/CorePayments/EligibilityAPI.swift | 52 ------- .../GraphQL/FundingEligibilityIntent.swift | 8 -- .../GraphQL/FundingEligibilityQuery.swift | 83 ----------- .../GraphQL/FundingEligibilityResponse.swift | 19 --- .../SupportedCountryCurrencyType.swift | 3 - .../GraphQL/SupportedPaymentMethodsType.swift | 5 - .../EligibilityAPI_Tests.swift | 135 ------------------ .../EligibilityParsing_Tests.swift | 87 ----------- .../GraphQLClient_Tests.swift | 24 ---- 11 files changed, 463 deletions(-) delete mode 100644 Sources/CorePayments/Eligibility.swift delete mode 100644 Sources/CorePayments/EligibilityAPI.swift delete mode 100644 Sources/CorePayments/Networking/GraphQL/FundingEligibilityIntent.swift delete mode 100644 Sources/CorePayments/Networking/GraphQL/FundingEligibilityQuery.swift delete mode 100644 Sources/CorePayments/Networking/GraphQL/FundingEligibilityResponse.swift delete mode 100644 Sources/CorePayments/Networking/GraphQL/SupportedCountryCurrencyType.swift delete mode 100644 Sources/CorePayments/Networking/GraphQL/SupportedPaymentMethodsType.swift delete mode 100644 UnitTests/PaymentsCoreTests/EligibilityAPI_Tests.swift delete mode 100644 UnitTests/PaymentsCoreTests/EligibilityParsing_Tests.swift diff --git a/PayPal.xcodeproj/project.pbxproj b/PayPal.xcodeproj/project.pbxproj index f21e004e8..accd623d5 100644 --- a/PayPal.xcodeproj/project.pbxproj +++ b/PayPal.xcodeproj/project.pbxproj @@ -17,7 +17,6 @@ 3B80D50C2A27979000D2EAC4 /* FailingJSONEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B80D50B2A27979000D2EAC4 /* FailingJSONEncoder.swift */; }; 3D1763A22720722A00652E1C /* CardResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1763A12720722A00652E1C /* CardResult.swift */; }; 3DC42BA927187E8300B71645 /* ErrorResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC42BA827187E8300B71645 /* ErrorResponse.swift */; }; - 537804FD28760BE2006442BD /* EligibilityAPI_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537804FC28760BE2006442BD /* EligibilityAPI_Tests.swift */; }; 53A2A4E228A182AC0093441C /* NativeCheckoutProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A2A4E128A182AC0093441C /* NativeCheckoutProvider.swift */; }; 80132D7229008C000088D30D /* TestShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 80E743F8270E40CE00BACECA /* TestShared.framework */; }; 8021B69029144E6D000FBC54 /* PayPalCoreConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8021B68F29144E6D000FBC54 /* PayPalCoreConstants.swift */; }; @@ -132,18 +131,10 @@ CBC16DE029EE2F8200307117 /* PayPalNativePaysheetAction_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC16DDF29EE2F8200307117 /* PayPalNativePaysheetAction_Tests.swift */; }; CBC16DF629EECCB900307117 /* NativeCheckoutProvider_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC16DF529EECCB900307117 /* NativeCheckoutProvider_Tests.swift */; }; CBD6004728D0C24A00C3EFF6 /* MockPayPalDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD6004628D0C24900C3EFF6 /* MockPayPalDelegate.swift */; }; - E6022E772857C6BE008B0E27 /* Eligibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5324C2D0283EB45A00DBC34F /* Eligibility.swift */; }; - E6022E782857C6BE008B0E27 /* EligibilityAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5324C2CB283EB40F00DBC34F /* EligibilityAPI.swift */; }; - E6022E792857C6BE008B0E27 /* FundingEligibilityIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64623292836A90E008AC8E1 /* FundingEligibilityIntent.swift */; }; - E6022E7A2857C6BE008B0E27 /* SupportedCountryCurrencyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E646232B2836A9A9008AC8E1 /* SupportedCountryCurrencyType.swift */; }; - E6022E7B2857C6BE008B0E27 /* SupportedPaymentMethodsType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E646232D2836A9CF008AC8E1 /* SupportedPaymentMethodsType.swift */; }; - E6022E7C2857C6BE008B0E27 /* FundingEligibilityResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E646232F2836AA71008AC8E1 /* FundingEligibilityResponse.swift */; }; - E6022E7F2857C6BE008B0E27 /* FundingEligibilityQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = E646231E2836A645008AC8E1 /* FundingEligibilityQuery.swift */; }; E6022E802857C6BE008B0E27 /* GraphQLQueryResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64623222836A69E008AC8E1 /* GraphQLQueryResponse.swift */; }; E6022E822857C6BE008B0E27 /* GraphQLError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64623262836A81E008AC8E1 /* GraphQLError.swift */; }; E6022E832857C6BE008B0E27 /* GraphQLQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = E646231B28369B9B008AC8E1 /* GraphQLQuery.swift */; }; E6022E842857C6BE008B0E27 /* GraphQLClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64623372836AFC1008AC8E1 /* GraphQLClient.swift */; }; - E6022E872857C81E008B0E27 /* EligibilityParsing_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6022E862857C81E008B0E27 /* EligibilityParsing_Tests.swift */; }; E64763712899B60C00074113 /* MockAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64763702899B60C00074113 /* MockAPIClient.swift */; }; E699EC16285A388E0044A753 /* GraphQLClient_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E699EC15285A388E0044A753 /* GraphQLClient_Tests.swift */; }; /* End PBXBuildFile section */ @@ -209,9 +200,6 @@ 3D25238127344F330099E4EB /* NativeCheckoutStartable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NativeCheckoutStartable.swift; path = Sources/PayPalNativePayments/NativeCheckoutStartable.swift; sourceTree = SOURCE_ROOT; }; 3D25238B273979170099E4EB /* MockNativeCheckoutProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNativeCheckoutProvider.swift; sourceTree = ""; }; 3DC42BA827187E8300B71645 /* ErrorResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorResponse.swift; sourceTree = ""; }; - 5324C2CB283EB40F00DBC34F /* EligibilityAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EligibilityAPI.swift; sourceTree = ""; }; - 5324C2D0283EB45A00DBC34F /* Eligibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Eligibility.swift; sourceTree = ""; }; - 537804FC28760BE2006442BD /* EligibilityAPI_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EligibilityAPI_Tests.swift; sourceTree = ""; }; 53A2A4E128A182AC0093441C /* NativeCheckoutProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeCheckoutProvider.swift; sourceTree = ""; }; 8021B68F29144E6D000FBC54 /* PayPalCoreConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalCoreConstants.swift; sourceTree = ""; }; 802C4A732945670400896A5D /* MockHTTP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockHTTP.swift; sourceTree = ""; }; @@ -320,15 +308,9 @@ CBC16DDF29EE2F8200307117 /* PayPalNativePaysheetAction_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalNativePaysheetAction_Tests.swift; sourceTree = ""; }; CBC16DF529EECCB900307117 /* NativeCheckoutProvider_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeCheckoutProvider_Tests.swift; sourceTree = ""; }; CBD6004628D0C24900C3EFF6 /* MockPayPalDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPayPalDelegate.swift; sourceTree = ""; }; - E6022E862857C81E008B0E27 /* EligibilityParsing_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EligibilityParsing_Tests.swift; sourceTree = ""; }; E646231B28369B9B008AC8E1 /* GraphQLQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLQuery.swift; sourceTree = ""; }; - E646231E2836A645008AC8E1 /* FundingEligibilityQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FundingEligibilityQuery.swift; sourceTree = ""; }; E64623222836A69E008AC8E1 /* GraphQLQueryResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLQueryResponse.swift; sourceTree = ""; }; E64623262836A81E008AC8E1 /* GraphQLError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLError.swift; sourceTree = ""; }; - E64623292836A90E008AC8E1 /* FundingEligibilityIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FundingEligibilityIntent.swift; sourceTree = ""; }; - E646232B2836A9A9008AC8E1 /* SupportedCountryCurrencyType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedCountryCurrencyType.swift; sourceTree = ""; }; - E646232D2836A9CF008AC8E1 /* SupportedPaymentMethodsType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedPaymentMethodsType.swift; sourceTree = ""; }; - E646232F2836AA71008AC8E1 /* FundingEligibilityResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FundingEligibilityResponse.swift; sourceTree = ""; }; E64623372836AFC1008AC8E1 /* GraphQLClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLClient.swift; sourceTree = ""; }; E64763702899B60C00074113 /* MockAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAPIClient.swift; sourceTree = ""; }; E699EC15285A388E0044A753 /* GraphQLClient_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLClient_Tests.swift; sourceTree = ""; }; @@ -485,11 +467,9 @@ 8036C1DE270F9BCF00C0F091 /* PaymentsCoreTests */ = { isa = PBXGroup; children = ( - 537804FC28760BE2006442BD /* EligibilityAPI_Tests.swift */, 8036C1E0270F9BE700C0F091 /* APIClient_Tests.swift */, 802C4A752945676E00896A5D /* AnalyticsService_Tests.swift */, 8036C1E1270F9BE700C0F091 /* Environment_Tests.swift */, - E6022E862857C81E008B0E27 /* EligibilityParsing_Tests.swift */, E699EC15285A388E0044A753 /* GraphQLClient_Tests.swift */, 808EEA80291321FE001B6765 /* AnalyticsEventRequest_Tests.swift */, 80FC261C29847AC7008EC841 /* HTTP_Tests.swift */, @@ -511,8 +491,6 @@ isa = PBXGroup; children = ( BE4F784827EB629100FF4C0E /* WebAuthenticationSession.swift */, - 5324C2D0283EB45A00DBC34F /* Eligibility.swift */, - 5324C2CB283EB40F00DBC34F /* EligibilityAPI.swift */, 06CE009826F3D1660000CC46 /* CoreConfig.swift */, 065A4DBB26FCD8080007014A /* CoreSDKError.swift */, BEA100E526EF9EDA0036A6A5 /* Networking */, @@ -725,15 +703,10 @@ E646231928369B71008AC8E1 /* GraphQL */ = { isa = PBXGroup; children = ( - E64623292836A90E008AC8E1 /* FundingEligibilityIntent.swift */, - E646231E2836A645008AC8E1 /* FundingEligibilityQuery.swift */, - E646232F2836AA71008AC8E1 /* FundingEligibilityResponse.swift */, E64623372836AFC1008AC8E1 /* GraphQLClient.swift */, E64623262836A81E008AC8E1 /* GraphQLError.swift */, E646231B28369B9B008AC8E1 /* GraphQLQuery.swift */, E64623222836A69E008AC8E1 /* GraphQLQueryResponse.swift */, - E646232B2836A9A9008AC8E1 /* SupportedCountryCurrencyType.swift */, - E646232D2836A9CF008AC8E1 /* SupportedPaymentMethodsType.swift */, ); path = GraphQL; sourceTree = ""; @@ -1255,13 +1228,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 537804FD28760BE2006442BD /* EligibilityAPI_Tests.swift in Sources */, E699EC16285A388E0044A753 /* GraphQLClient_Tests.swift in Sources */, 808EEA81291321FE001B6765 /* AnalyticsEventRequest_Tests.swift in Sources */, 8036C1E5270F9BE700C0F091 /* Environment_Tests.swift in Sources */, 80FC261D29847AC7008EC841 /* HTTP_Tests.swift in Sources */, 802C4A762945676E00896A5D /* AnalyticsService_Tests.swift in Sources */, - E6022E872857C81E008B0E27 /* EligibilityParsing_Tests.swift in Sources */, 807BF5912A2A5D48002F32B3 /* HTTPResponseParser_Tests.swift in Sources */, 8036C1E4270F9BE700C0F091 /* APIClient_Tests.swift in Sources */, ); @@ -1271,14 +1242,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E6022E772857C6BE008B0E27 /* Eligibility.swift in Sources */, - E6022E782857C6BE008B0E27 /* EligibilityAPI.swift in Sources */, - E6022E792857C6BE008B0E27 /* FundingEligibilityIntent.swift in Sources */, - E6022E7A2857C6BE008B0E27 /* SupportedCountryCurrencyType.swift in Sources */, - E6022E7B2857C6BE008B0E27 /* SupportedPaymentMethodsType.swift in Sources */, - E6022E7C2857C6BE008B0E27 /* FundingEligibilityResponse.swift in Sources */, 807C5E67291027D400ECECD8 /* AnalyticsEventRequest.swift in Sources */, - E6022E7F2857C6BE008B0E27 /* FundingEligibilityQuery.swift in Sources */, E6022E802857C6BE008B0E27 /* GraphQLQueryResponse.swift in Sources */, 80E643832A1EBBD2008FD705 /* HTTPResponse.swift in Sources */, 807C5E6929102D9800ECECD8 /* AnalyticsEventData.swift in Sources */, diff --git a/Sources/CorePayments/Eligibility.swift b/Sources/CorePayments/Eligibility.swift deleted file mode 100644 index faf74a0fa..000000000 --- a/Sources/CorePayments/Eligibility.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -/// Class that represents merchant's eligibility for a set of payment methods -struct Eligibility { - - var isVenmoEligible: Bool - var isPaypalEligible: Bool - var isPaypalCreditEligible: Bool - var isPayLaterEligible: Bool - var isCreditCardEligible: Bool -} diff --git a/Sources/CorePayments/EligibilityAPI.swift b/Sources/CorePayments/EligibilityAPI.swift deleted file mode 100644 index e26411b4a..000000000 --- a/Sources/CorePayments/EligibilityAPI.swift +++ /dev/null @@ -1,52 +0,0 @@ -import Foundation - -/// API that return merchant's eligibility for different payment methods: Venmo, PayPal, PayPal Credit, Pay Later & credit card -class EligibilityAPI { - - private let graphQLClient: GraphQLClient - private let apiClient: APIClient - private let coreConfig: CoreConfig - - /// Initialize the eligibility API to check for payment methods eligibility - /// - Parameter coreConfig: configuration object - convenience init(coreConfig: CoreConfig) { - self.init( - coreConfig: coreConfig, - apiClient: APIClient(coreConfig: coreConfig), - graphQLClient: GraphQLClient(environment: coreConfig.environment) - ) - } - - init(coreConfig: CoreConfig, apiClient: APIClient, graphQLClient: GraphQLClient) { - self.coreConfig = coreConfig - self.apiClient = apiClient - self.graphQLClient = graphQLClient - } - - /// Checks merchants eligibility for different payment methods. - /// - Returns: a result object with either eligibility or an error - func checkEligibility() async throws -> Result { - let clientID = coreConfig.clientID - let fundingEligibilityQuery = FundingEligibilityQuery( - clientID: clientID, - fundingEligibilityIntent: FundingEligibilityIntent.CAPTURE, - currencyCode: SupportedCountryCurrencyType.USD, - enableFunding: [SupportedPaymentMethodsType.VENMO] - ) - let response: GraphQLQueryResponse - = try await graphQLClient.executeQuery(query: fundingEligibilityQuery) - if response.data == nil { - return Result.failure(GraphQLError(message: "error fetching eligibility", extensions: nil)) - } else { - let eligibility = response.data?.fundingEligibility - return Result.success( - Eligibility( - isVenmoEligible: eligibility?.venmo.eligible ?? false, - isPaypalEligible: eligibility?.paypal.eligible ?? false, - isPaypalCreditEligible: eligibility?.credit.eligible ?? false, - isPayLaterEligible: eligibility?.paylater.eligible ?? false, - isCreditCardEligible: eligibility?.card.eligible ?? false) - ) - } - } -} diff --git a/Sources/CorePayments/Networking/GraphQL/FundingEligibilityIntent.swift b/Sources/CorePayments/Networking/GraphQL/FundingEligibilityIntent.swift deleted file mode 100644 index cf39d30b3..000000000 --- a/Sources/CorePayments/Networking/GraphQL/FundingEligibilityIntent.swift +++ /dev/null @@ -1,8 +0,0 @@ -enum FundingEligibilityIntent: String { - case SALE - case CAPTURE - case AUTHORIZE - case ORDER - case TOKENIZE - case SUBSCRIPTION -} diff --git a/Sources/CorePayments/Networking/GraphQL/FundingEligibilityQuery.swift b/Sources/CorePayments/Networking/GraphQL/FundingEligibilityQuery.swift deleted file mode 100644 index 47d512cf1..000000000 --- a/Sources/CorePayments/Networking/GraphQL/FundingEligibilityQuery.swift +++ /dev/null @@ -1,83 +0,0 @@ -class FundingEligibilityQuery: GraphQLQuery { - - var query: String { - return rawQuery - } - - var variables: [String: Any] { - return [ - paramClientId: clientID, - paramIntent: fundingEligibilityIntent.rawValue, - paramCurrency: currencyCode.rawValue, - paramEnableFunding: enableFunding.toStringArray() - ] - } - - let clientID: String - let fundingEligibilityIntent: FundingEligibilityIntent - let currencyCode: SupportedCountryCurrencyType - var enableFunding: [SupportedPaymentMethodsType] - - init( - clientID: String, - fundingEligibilityIntent: FundingEligibilityIntent, - currencyCode: SupportedCountryCurrencyType, - enableFunding: [SupportedPaymentMethodsType] - ) { - self.clientID = clientID - self.fundingEligibilityIntent = fundingEligibilityIntent - self.currencyCode = currencyCode - self.enableFunding = enableFunding - } - - let paramClientId = "clientId" - let paramIntent = "intent" - let paramCurrency = "currency" - let paramEnableFunding = "enableFunding" - - private let rawQuery = """ - query getEligibility( - $clientId: String!, - $intent: FundingEligibilityIntent!, - $currency: SupportedCountryCurrencies!, - $enableFunding: [SupportedPaymentMethodsType] - ){ - fundingEligibility( - clientId: $clientId, - intent: $intent - currency: $currency, - enableFunding: $enableFunding){ - venmo{ - eligible - reasons - } - card{ - eligible - } - paypal{ - eligible - reasons - } - paylater{ - eligible - reasons - } - credit{ - eligible - reasons - } - } - } - """ -} - -extension Array -where Element == SupportedPaymentMethodsType { - func toStringArray() -> [String] { - var stringArray: [String] = [] - for element in self { - stringArray += [element.rawValue] - } - return stringArray - } -} diff --git a/Sources/CorePayments/Networking/GraphQL/FundingEligibilityResponse.swift b/Sources/CorePayments/Networking/GraphQL/FundingEligibilityResponse.swift deleted file mode 100644 index 642fb88a9..000000000 --- a/Sources/CorePayments/Networking/GraphQL/FundingEligibilityResponse.swift +++ /dev/null @@ -1,19 +0,0 @@ -struct FundingEligibilityResponse: Codable { - - let fundingEligibility: FundingEligibility -} - -struct FundingEligibility: Codable { - - let venmo: SupportedPaymentMethodsTypeEligibility - let card: SupportedPaymentMethodsTypeEligibility - let paypal: SupportedPaymentMethodsTypeEligibility - let paylater: SupportedPaymentMethodsTypeEligibility - let credit: SupportedPaymentMethodsTypeEligibility -} - -struct SupportedPaymentMethodsTypeEligibility: Codable { - - let eligible: Bool - let reasons: [String]? -} diff --git a/Sources/CorePayments/Networking/GraphQL/SupportedCountryCurrencyType.swift b/Sources/CorePayments/Networking/GraphQL/SupportedCountryCurrencyType.swift deleted file mode 100644 index 71a31c60d..000000000 --- a/Sources/CorePayments/Networking/GraphQL/SupportedCountryCurrencyType.swift +++ /dev/null @@ -1,3 +0,0 @@ -enum SupportedCountryCurrencyType: String { - case USD -} diff --git a/Sources/CorePayments/Networking/GraphQL/SupportedPaymentMethodsType.swift b/Sources/CorePayments/Networking/GraphQL/SupportedPaymentMethodsType.swift deleted file mode 100644 index a1a2358e2..000000000 --- a/Sources/CorePayments/Networking/GraphQL/SupportedPaymentMethodsType.swift +++ /dev/null @@ -1,5 +0,0 @@ -enum SupportedPaymentMethodsType: String { - case VENMO - case CREDIT - case PAYLATER -} diff --git a/UnitTests/PaymentsCoreTests/EligibilityAPI_Tests.swift b/UnitTests/PaymentsCoreTests/EligibilityAPI_Tests.swift deleted file mode 100644 index 70e96b0a0..000000000 --- a/UnitTests/PaymentsCoreTests/EligibilityAPI_Tests.swift +++ /dev/null @@ -1,135 +0,0 @@ -import XCTest -@testable import CorePayments -@testable import TestShared - -class EligibilityAPI_Tests: XCTestCase { - - let mockClientID = "mockClientId" - let mockURLSession = MockURLSession() - var coreConfig: CoreConfig! - var graphQLClient: GraphQLClient! - var eligibilityAPI: EligibilityAPI! - var apiClient: MockAPIClient! - - override func setUp() { - super.setUp() - coreConfig = CoreConfig(clientID: mockClientID, environment: .sandbox) - apiClient = MockAPIClient(coreConfig: coreConfig) - graphQLClient = GraphQLClient(environment: .sandbox, urlSession: mockURLSession) - } - - func testCheckEligibilityWithSuccessResponse() async throws { - mockURLSession.cannedError = nil - mockURLSession.cannedURLResponse = HTTPURLResponse( - // swiftlint:disable:next force_unwrapping - url: URL(string: "www.fake.com")!, - statusCode: 200, - httpVersion: "1", - headerFields: ["Paypal-Debug-Id": "454532"] - ) - mockURLSession.cannedJSONData = validFundingEligibilityResponse - eligibilityAPI = EligibilityAPI(coreConfig: coreConfig, apiClient: apiClient, graphQLClient: graphQLClient) - let result = try await eligibilityAPI.checkEligibility() - switch result { - case .success(let eligibility): - XCTAssertTrue(eligibility.isVenmoEligible) - XCTAssertTrue(eligibility.isPaypalEligible) - XCTAssertFalse(eligibility.isCreditCardEligible) - case .failure(let error): - XCTAssertNil(error) - } - } - func testCheckEligibilityErrorResponse() async throws { - mockURLSession.cannedURLResponse = HTTPURLResponse( - url: URL(string: "www.fake.com")!, - statusCode: 200, - httpVersion: "1", - headerFields: ["Paypal-Debug-Id": "454532"] - ) - mockURLSession.cannedJSONData = notValidFundingEligibilityResponse - eligibilityAPI = EligibilityAPI(coreConfig: coreConfig, apiClient: apiClient, graphQLClient: graphQLClient) - let result = try await eligibilityAPI.checkEligibility() - switch result { - case .success(let eligibility): - XCTAssertNil(eligibility) - case .failure(let failure): - XCTAssertNotNil(failure) - } - } - let notValidFundingEligibilityResponse = """ - { - - } - """ - let validFundingEligibilityResponse = """ - { - "data": { - "fundingEligibility": { - "venmo": { - "eligible": true, - "reasons": [ - "isPaymentMethodEnabled", - "isMSPEligible", - "isUnilateralPaymentSupported", - "isEnvEligible", - "isMerchantCountryEligible", - "isBuyerCountryEligible", - "isIntentEligible", - "isCommitEligible", - "isVaultEligible", - "isCurrencyEligible", - "isPaymentMethodDisabled", - "isDeviceEligible", - "VENMO OPT-IN WITH ENABLE_FUNDING" - ] - }, - "card": { - "eligible": false - }, - "paypal": { - "eligible": true, - "reasons": [ - "isPaymentMethodEnabled", - "isMSPEligible", - "isUnilateralPaymentSupported", - "isEnvEligible", - "isMerchantCountryEligible", - "isBuyerCountryEligible", - "isIntentEligible", - "isCommitEligible", - "isVaultEligible", - "isCurrencyEligible", - "isPaymentMethodDisabled", - "isDeviceEligible" - ] - }, - "paylater": { - "eligible": true, - "reasons": [ - "isPaymentMethodEnabled", - "isMSPEligible", - "isUnilateralPaymentSupported", - "isEnvEligible", - "isMerchantCountryEligible", - "isBuyerCountryEligible", - "isIntentEligible", - "isCommitEligible", - "isVaultEligible", - "isCurrencyEligible", - "isPaymentMethodDisabled", - "isDeviceEligible", - "CRC OFFERS SERV CALLED: Trmt_xo_xobuyernodeserv_call_crcoffersserv", - "CRC OFFERS SERV ELIGIBLE" - ] - }, - "credit": { - "eligible": false, - "reasons": [ - "INELIGIBLE DUE TO PAYLATER ELIGIBLE" - ] - } - } - } - } - """ -} diff --git a/UnitTests/PaymentsCoreTests/EligibilityParsing_Tests.swift b/UnitTests/PaymentsCoreTests/EligibilityParsing_Tests.swift deleted file mode 100644 index a84ef8375..000000000 --- a/UnitTests/PaymentsCoreTests/EligibilityParsing_Tests.swift +++ /dev/null @@ -1,87 +0,0 @@ -import XCTest -@testable import CorePayments - -class EligibilityParsing_Tests: XCTestCase { - - func testFundingEligibility_matchesResponse() throws { - if let jsonData = validFundingEligibilityResponse.data(using: .utf8) { - let fundingEligibility = try JSONDecoder().decode(FundingEligibilityResponse.self, from: jsonData).fundingEligibility - XCTAssertEqual(fundingEligibility.venmo.eligible, true) - XCTAssertEqual(fundingEligibility.card.eligible, false) - XCTAssertEqual(fundingEligibility.paypal.eligible, true) - XCTAssertEqual(fundingEligibility.paylater.eligible, true) - } else { - XCTFail() - } - } - - let validFundingEligibilityResponse = """ - { - "fundingEligibility": { - "venmo": { - "eligible": true, - "reasons": [ - "isPaymentMethodEnabled", - "isMSPEligible", - "isUnilateralPaymentSupported", - "isEnvEligible", - "isMerchantCountryEligible", - "isBuyerCountryEligible", - "isIntentEligible", - "isCommitEligible", - "isVaultEligible", - "isCurrencyEligible", - "isPaymentMethodDisabled", - "isDeviceEligible", - "VENMO OPT-IN WITH ENABLE_FUNDING" - ] - }, - "card": { - "eligible": false - }, - "paypal": { - "eligible": true, - "reasons": [ - "isPaymentMethodEnabled", - "isMSPEligible", - "isUnilateralPaymentSupported", - "isEnvEligible", - "isMerchantCountryEligible", - "isBuyerCountryEligible", - "isIntentEligible", - "isCommitEligible", - "isVaultEligible", - "isCurrencyEligible", - "isPaymentMethodDisabled", - "isDeviceEligible" - ] - }, - "paylater": { - "eligible": true, - "reasons": [ - "isPaymentMethodEnabled", - "isMSPEligible", - "isUnilateralPaymentSupported", - "isEnvEligible", - "isMerchantCountryEligible", - "isBuyerCountryEligible", - "isIntentEligible", - "isCommitEligible", - "isVaultEligible", - "isCurrencyEligible", - "isPaymentMethodDisabled", - "isDeviceEligible", - "CRC OFFERS SERV CALLED: Trmt_xo_xobuyernodeserv_call_crcoffersserv", - "CRC OFFERS SERV ELIGIBLE" - ] - }, - "credit": { - "eligible": false, - "reasons": [ - "INELIGIBLE DUE TO PAYLATER ELIGIBLE" - ] - } - } - } - """ -} diff --git a/UnitTests/PaymentsCoreTests/GraphQLClient_Tests.swift b/UnitTests/PaymentsCoreTests/GraphQLClient_Tests.swift index f1d65f778..426ffc8e5 100644 --- a/UnitTests/PaymentsCoreTests/GraphQLClient_Tests.swift +++ b/UnitTests/PaymentsCoreTests/GraphQLClient_Tests.swift @@ -26,15 +26,6 @@ class GraphQLClient_Tests: XCTestCase { mockURLSession.cannedJSONData = nil graphQLClient = GraphQLClient(environment: .sandbox, urlSession: mockURLSession) - - let fundingEligibilityQuery = FundingEligibilityQuery( - clientID: mockClientID, - fundingEligibilityIntent: FundingEligibilityIntent.CAPTURE, - currencyCode: SupportedCountryCurrencyType.USD, - enableFunding: [SupportedPaymentMethodsType.VENMO] - ) - - graphQLQuery = fundingEligibilityQuery } // MARK: - fetch() tests @@ -49,14 +40,6 @@ class GraphQLClient_Tests: XCTestCase { httpVersion: "1", headerFields: ["Paypal-Debug-Id": "454532"] ) - - do { - let response: GraphQLQueryResponse = try await graphQLClient.executeQuery(query: graphQLQuery) - XCTAssertTrue(response.data == nil) - } catch { - print(error.localizedDescription) - XCTFail("Expected success response") - } } func testGraphQLClient_verifyNonEmptyResponse() async throws { @@ -70,13 +53,6 @@ class GraphQLClient_Tests: XCTestCase { httpVersion: "1", headerFields: ["Paypal-Debug-Id": "454532"] ) - - do { - let response: GraphQLQueryResponse = try await graphQLClient.executeQuery(query: graphQLQuery) - XCTAssertTrue(response.data != nil) - } catch { - XCTAssertTrue(!error.localizedDescription.isEmpty) - } } let graphQLQueryResponseWithData = """ From c435d0a5cfafc2626b8768b8f50ba04a3fcd9d54 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Thu, 10 Aug 2023 14:42:47 -0500 Subject: [PATCH 17/30] PR Feedback - move optional param to end of HTTPRequest property list & alphabetize --- Sources/CorePayments/Networking/APIClient.swift | 6 +++--- .../CorePayments/Networking/HTTPRequest.swift | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index 93d185814..8e9a2a495 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -32,10 +32,10 @@ public class APIClient { let url = try constructURL(path: request.path, queryParameters: request.queryParameters ?? [:]) // cleaner way let httpRequest = HTTPRequest( - url: url, + headers: request.headers, method: request.method, - body: request.body, - headers: request.headers + url: url, + body: request.body ) return try await http.performRequest(httpRequest) diff --git a/Sources/CorePayments/Networking/HTTPRequest.swift b/Sources/CorePayments/Networking/HTTPRequest.swift index 53d7cf08e..1f8862fa4 100644 --- a/Sources/CorePayments/Networking/HTTPRequest.swift +++ b/Sources/CorePayments/Networking/HTTPRequest.swift @@ -3,21 +3,21 @@ import Foundation /// :nodoc: public struct HTTPRequest { - let url: URL + let headers: [HTTPHeader: String] let method: HTTPMethod + let url: URL let body: Data? - let headers: [HTTPHeader: String] - + /// :nodoc: public init( - url: URL, + headers: [HTTPHeader: String], method: HTTPMethod, - body: Data?, - headers: [HTTPHeader: String] + url: URL, + body: Data? ) { - self.url = url + self.headers = headers self.method = method + self.url = url self.body = body - self.headers = headers } } From 508b845746b3c94b0293f94feb91045f3d64a215 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 11 Aug 2023 10:17:41 -0500 Subject: [PATCH 18/30] WIP --- .../APIRequests/CheckoutOrdersAPI.swift | 10 ++++ .../CorePayments/Networking/APIClient.swift | 49 ++++++++++++++++--- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift b/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift index 066eea4e3..e9a30294e 100644 --- a/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift +++ b/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift @@ -16,6 +16,16 @@ class CheckoutOrdersAPI { init(coreConfig: CoreConfig) { self.coreConfig = coreConfig + + let apiClient = APIClient(coreConfig: coreConfig) + Task { + do { + let response = try await apiClient.fetch() + print(response) + } catch { + // + } + } } // MARK: - Internal Methods diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index b5974150b..9f9d2997e 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -43,7 +43,8 @@ public class APIClient { // TODO: - Add GraphQL equivalent request type & function // public func fetch(request: GraphQLRequest) async throws -> HTTPResponse { } - public func fetch() throws { + public func fetch() async throws -> HTTPResponse { + // Query String let queryString = """ mutation UpdateVaultSetupToken( $clientID: String!, @@ -64,23 +65,48 @@ public class APIClient { } """ - // TODO: - Move JSON encoding into custom class, similar to HTTPResponseParser + // Encodable Variables let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase let variables = try encoder.encode(VaultDataEncodableVariables()) - // Combine varialbes & query into single post body - let graphQLRequest = GraphQLRequest( - queryNameForURL: "UpdateVaultSetupToken", - query: queryString, - variables: variables - ) + // Combine variables & query into single post body + let post = GraphQLHTTPPostBody(query: queryString, variables: variables) + let postData = try JSONEncoder().encode(VaultDataEncodableVariables()) + + // let graphQLRequest = GraphQLRequest( + // queryNameForURL: "UpdateVaultSetupToken", + // query: queryString, + // variables: variables + // ) + + let url = try constructGraphQLURL(queryName: "UpdateVaultSetupToken") // Construct HTTPRequest + let httpRequest = HTTPRequest( + headers: [.contentType: "application/json"], + method: .post, + url: url, + body: postData + ) + + return try await http.performRequest(httpRequest) } // MARK: - Private Methods + private func constructGraphQLURL(queryName: String? = nil) throws -> URL { + guard let queryName else { + return coreConfig.environment.graphQLURL + } + + if let url = URL(string: coreConfig.environment.graphQLURL.absoluteString + "?" + queryName) { + return url + } else { + throw CorePaymentsError.clientIDNotFoundError // TODO: - throw proper error type + } + } + private func constructURL(path: String, queryParameters: [String: String]) throws -> URL { let urlString = coreConfig.environment.baseURL.appendingPathComponent(path) var urlComponents = URLComponents(url: urlString, resolvingAgainstBaseURL: false) @@ -97,8 +123,15 @@ public class APIClient { } } +public struct GraphQLHTTPPostBody: Encodable { + + let query: String + let variables: Data +} + /// :nodoc: public struct GraphQLRequest { + let queryNameForURL: String? let query: String let variables: Data // Dictionary From 3a3262855d92bc0b1cf30d1454b6dfe3770baf19 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 11 Aug 2023 11:53:32 -0500 Subject: [PATCH 19/30] Cleanup - remove vault specific code & cleanup new graphQL implementation --- PayPal.xcodeproj/project.pbxproj | 9 +- .../CorePayments/Networking/APIClient.swift | 112 ++---------------- .../GraphQL/GraphQLHTTPPostBody.swift | 8 ++ .../Networking/GraphQL/GraphQLRequest.swift | 9 ++ .../Networking/HTTPResponseParser.swift | 12 +- 5 files changed, 42 insertions(+), 108 deletions(-) create mode 100644 Sources/CorePayments/Networking/GraphQL/GraphQLHTTPPostBody.swift create mode 100644 Sources/CorePayments/Networking/GraphQL/GraphQLRequest.swift diff --git a/PayPal.xcodeproj/project.pbxproj b/PayPal.xcodeproj/project.pbxproj index c3dc0b6f8..fbfded0c0 100644 --- a/PayPal.xcodeproj/project.pbxproj +++ b/PayPal.xcodeproj/project.pbxproj @@ -38,6 +38,8 @@ 807BF58F2A2A5D19002F32B3 /* HTTPResponseParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807BF58E2A2A5D19002F32B3 /* HTTPResponseParser.swift */; }; 807BF5912A2A5D48002F32B3 /* HTTPResponseParser_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807BF5902A2A5D48002F32B3 /* HTTPResponseParser_Tests.swift */; }; 807C5E6929102D9800ECECD8 /* AnalyticsEventData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807C5E6829102D9800ECECD8 /* AnalyticsEventData.swift */; }; + 807D56AC2A869044009E591D /* GraphQLHTTPPostBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807D56AB2A869044009E591D /* GraphQLHTTPPostBody.swift */; }; + 807D56AE2A869064009E591D /* GraphQLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807D56AD2A869064009E591D /* GraphQLRequest.swift */; }; 808EEA81291321FE001B6765 /* AnalyticsEventRequest_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 808EEA80291321FE001B6765 /* AnalyticsEventRequest_Tests.swift */; }; 80D0C1382731CC9B00548A3D /* PayPalNativeCheckoutClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE7116152723227200165069 /* PayPalNativeCheckoutClient.swift */; }; 80DB2F762980795D00CFB86A /* CorePaymentsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DB2F752980795D00CFB86A /* CorePaymentsError.swift */; }; @@ -219,6 +221,8 @@ 807BF58E2A2A5D19002F32B3 /* HTTPResponseParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPResponseParser.swift; sourceTree = ""; }; 807BF5902A2A5D48002F32B3 /* HTTPResponseParser_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPResponseParser_Tests.swift; sourceTree = ""; }; 807C5E6829102D9800ECECD8 /* AnalyticsEventData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsEventData.swift; sourceTree = ""; }; + 807D56AB2A869044009E591D /* GraphQLHTTPPostBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLHTTPPostBody.swift; sourceTree = ""; }; + 807D56AD2A869064009E591D /* GraphQLRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLRequest.swift; sourceTree = ""; }; 808EEA80291321FE001B6765 /* AnalyticsEventRequest_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsEventRequest_Tests.swift; sourceTree = ""; }; 80B9F85126B8750000D67843 /* CorePayments.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CorePayments.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 80DB2F752980795D00CFB86A /* CorePaymentsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CorePaymentsError.swift; sourceTree = ""; }; @@ -716,6 +720,8 @@ E64623262836A81E008AC8E1 /* GraphQLError.swift */, E646231B28369B9B008AC8E1 /* GraphQLQuery.swift */, E64623222836A69E008AC8E1 /* GraphQLQueryResponse.swift */, + 807D56AB2A869044009E591D /* GraphQLHTTPPostBody.swift */, + 807D56AD2A869064009E591D /* GraphQLRequest.swift */, ); path = GraphQL; sourceTree = ""; @@ -1251,7 +1257,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 807C5E67291027D400ECECD8 /* AnalyticsEventRequest.swift in Sources */, E6022E802857C6BE008B0E27 /* GraphQLQueryResponse.swift in Sources */, 80E643832A1EBBD2008FD705 /* HTTPResponse.swift in Sources */, 807C5E6929102D9800ECECD8 /* AnalyticsEventData.swift in Sources */, @@ -1273,11 +1278,13 @@ BC04837427B2FC7300FA7B46 /* URLSession+URLSessionProtocol.swift in Sources */, 80E2FDC32A8354AD0045593D /* RESTRequest.swift in Sources */, 3DC42BA927187E8300B71645 /* ErrorResponse.swift in Sources */, + 807D56AC2A869044009E591D /* GraphQLHTTPPostBody.swift in Sources */, 804E62822937EBCE004B9FEF /* HTTP.swift in Sources */, BC04836F27B2FB3600FA7B46 /* URLSessionProtocol.swift in Sources */, 065A4DBC26FCD8090007014A /* CoreSDKError.swift in Sources */, BEA100E726EF9EDA0036A6A5 /* APIClient.swift in Sources */, 807BF58F2A2A5D19002F32B3 /* HTTPResponseParser.swift in Sources */, + 807D56AE2A869064009E591D /* GraphQLRequest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index 9f9d2997e..ec1ac1bd7 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -41,48 +41,13 @@ public class APIClient { return try await http.performRequest(httpRequest) } - // TODO: - Add GraphQL equivalent request type & function -// public func fetch(request: GraphQLRequest) async throws -> HTTPResponse { } - public func fetch() async throws -> HTTPResponse { - // Query String - let queryString = """ - mutation UpdateVaultSetupToken( - $clientID: String!, - $vaultSetupToken: String!, - $paymentSource: PaymentSource - ) { - updateVaultSetupToken( - clientId: $clientID - vaultSetupToken: $vaultSetupToken - paymentSource: $paymentSource - ) { - id, - status, - links { - rel, href - } - } - } - """ - - // Encodable Variables - let encoder = JSONEncoder() - encoder.keyEncodingStrategy = .convertToSnakeCase - let variables = try encoder.encode(VaultDataEncodableVariables()) - - // Combine variables & query into single post body - let post = GraphQLHTTPPostBody(query: queryString, variables: variables) - let postData = try JSONEncoder().encode(VaultDataEncodableVariables()) - - // let graphQLRequest = GraphQLRequest( - // queryNameForURL: "UpdateVaultSetupToken", - // query: queryString, - // variables: variables - // ) - - let url = try constructGraphQLURL(queryName: "UpdateVaultSetupToken") - - // Construct HTTPRequest + /// :nodoc: + public func fetch(request: GraphQLRequest) async throws -> HTTPResponse { + let url = try constructGraphQLURL(queryName: request.queryNameForURL) + + let postBody = GraphQLHTTPPostBody(query: request.query, variables: request.variables) + let postData = try JSONEncoder().encode(postBody) + let httpRequest = HTTPRequest( headers: [.contentType: "application/json"], method: .post, @@ -122,66 +87,3 @@ public class APIClient { return url } } - -public struct GraphQLHTTPPostBody: Encodable { - - let query: String - let variables: Data -} - -/// :nodoc: -public struct GraphQLRequest { - - let queryNameForURL: String? - let query: String - let variables: Data // Dictionary -} - -struct VaultDataEncodableVariables: Encodable { - - // MARK: - Coding KEys - - private enum TopLevelKeys: String, CodingKey { - case clientID - case vaultSetupToken - case paymentSource - } - - private enum PaymentSourceKeys: String, CodingKey { - case card - } - - private enum CardKeys: String, CodingKey { - case number - case securityCode - case expiry - case name - } - - // MARK: - Initializer - - // TODO: - Migrate details from VaultRequest introduced in Victoria's PR - - // MARK: - Custom Encoder - - func encode(to encoder: Encoder) throws { - var topLevel = encoder.container(keyedBy: TopLevelKeys.self) - try topLevel.encode("fake-client-id", forKey: .clientID) - try topLevel.encode("fake-vault-setup-token", forKey: .vaultSetupToken) - - var paymentSource = topLevel.nestedContainer(keyedBy: PaymentSourceKeys.self, forKey: .paymentSource) - - var card = paymentSource.nestedContainer(keyedBy: CardKeys.self, forKey: .card) - try card.encode("4111 1111 1111 1111", forKey: .number) - try card.encode("123", forKey: .securityCode) - try card.encode("2027-09", forKey: .expiry) - try card.encode("Sammy", forKey: .name) - } -} - -// SAMPLE -//{ -// "query": "query GreetingQuery ($arg1: String) { hello (name: $arg1) { value } }", -// "operationName": "GreetingQuery", -// "variables": { "arg1": "Timothy" } -//} diff --git a/Sources/CorePayments/Networking/GraphQL/GraphQLHTTPPostBody.swift b/Sources/CorePayments/Networking/GraphQL/GraphQLHTTPPostBody.swift new file mode 100644 index 000000000..c01f15f91 --- /dev/null +++ b/Sources/CorePayments/Networking/GraphQL/GraphQLHTTPPostBody.swift @@ -0,0 +1,8 @@ +import Foundation + +/// The GraphQL query and variable details encoded to be sent in the POST body of a HTTP request +struct GraphQLHTTPPostBody: Encodable { + + let query: String + let variables: Data +} diff --git a/Sources/CorePayments/Networking/GraphQL/GraphQLRequest.swift b/Sources/CorePayments/Networking/GraphQL/GraphQLRequest.swift new file mode 100644 index 000000000..45b53bf0d --- /dev/null +++ b/Sources/CorePayments/Networking/GraphQL/GraphQLRequest.swift @@ -0,0 +1,9 @@ +import Foundation + +/// :nodoc: +public struct GraphQLRequest { + + let queryNameForURL: String? + let query: String + let variables: Data // Dictionary +} diff --git a/Sources/CorePayments/Networking/HTTPResponseParser.swift b/Sources/CorePayments/Networking/HTTPResponseParser.swift index 9517c89f2..8619a6969 100644 --- a/Sources/CorePayments/Networking/HTTPResponseParser.swift +++ b/Sources/CorePayments/Networking/HTTPResponseParser.swift @@ -24,11 +24,19 @@ public class HTTPResponseParser { } } else { do { - let errorData = try decoder.decode(ErrorResponse.self, from: data) - throw APIClientError.serverResponseError(errorData.readableDescription) + let errorData = try decoder.decode(GraphQLErrorResponse.self, from: data) + throw APIClientError.serverResponseError(errorData.error) + +// throw APIClientError.serverResponseError(errorData.readableDescription) } catch { throw APIClientError.jsonDecodingError(error.localizedDescription) } } } } + +struct GraphQLErrorResponse: Decodable { + + let error: String + let correlationId: String? +} From 2023a490226ea5958e7eae98a53e5908cc2da90f Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 11 Aug 2023 11:56:13 -0500 Subject: [PATCH 20/30] Remove unneeded GraphQL files & cleanup comments in HTTPResponseParser --- PayPal.xcodeproj/project.pbxproj | 16 ++--- .../Networking/GraphQL/GraphQLClient.swift | 58 ------------------- .../Networking/GraphQL/GraphQLError.swift | 10 ---- .../GraphQL/GraphQLErrorResponse.swift | 7 +++ .../Networking/GraphQL/GraphQLQuery.swift | 7 --- .../Networking/HTTPResponseParser.swift | 13 +---- 6 files changed, 14 insertions(+), 97 deletions(-) delete mode 100644 Sources/CorePayments/Networking/GraphQL/GraphQLClient.swift delete mode 100644 Sources/CorePayments/Networking/GraphQL/GraphQLError.swift create mode 100644 Sources/CorePayments/Networking/GraphQL/GraphQLErrorResponse.swift delete mode 100644 Sources/CorePayments/Networking/GraphQL/GraphQLQuery.swift diff --git a/PayPal.xcodeproj/project.pbxproj b/PayPal.xcodeproj/project.pbxproj index fbfded0c0..6a7834f93 100644 --- a/PayPal.xcodeproj/project.pbxproj +++ b/PayPal.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 807C5E6929102D9800ECECD8 /* AnalyticsEventData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807C5E6829102D9800ECECD8 /* AnalyticsEventData.swift */; }; 807D56AC2A869044009E591D /* GraphQLHTTPPostBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807D56AB2A869044009E591D /* GraphQLHTTPPostBody.swift */; }; 807D56AE2A869064009E591D /* GraphQLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807D56AD2A869064009E591D /* GraphQLRequest.swift */; }; + 807D56B02A869F97009E591D /* GraphQLErrorResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807D56AF2A869F97009E591D /* GraphQLErrorResponse.swift */; }; 808EEA81291321FE001B6765 /* AnalyticsEventRequest_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 808EEA80291321FE001B6765 /* AnalyticsEventRequest_Tests.swift */; }; 80D0C1382731CC9B00548A3D /* PayPalNativeCheckoutClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE7116152723227200165069 /* PayPalNativeCheckoutClient.swift */; }; 80DB2F762980795D00CFB86A /* CorePaymentsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DB2F752980795D00CFB86A /* CorePaymentsError.swift */; }; @@ -137,9 +138,6 @@ CBC16DF629EECCB900307117 /* NativeCheckoutProvider_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC16DF529EECCB900307117 /* NativeCheckoutProvider_Tests.swift */; }; CBD6004728D0C24A00C3EFF6 /* MockPayPalDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD6004628D0C24900C3EFF6 /* MockPayPalDelegate.swift */; }; E6022E802857C6BE008B0E27 /* GraphQLQueryResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64623222836A69E008AC8E1 /* GraphQLQueryResponse.swift */; }; - E6022E822857C6BE008B0E27 /* GraphQLError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64623262836A81E008AC8E1 /* GraphQLError.swift */; }; - E6022E832857C6BE008B0E27 /* GraphQLQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = E646231B28369B9B008AC8E1 /* GraphQLQuery.swift */; }; - E6022E842857C6BE008B0E27 /* GraphQLClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64623372836AFC1008AC8E1 /* GraphQLClient.swift */; }; E64763712899B60C00074113 /* MockAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64763702899B60C00074113 /* MockAPIClient.swift */; }; E699EC16285A388E0044A753 /* GraphQLClient_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E699EC15285A388E0044A753 /* GraphQLClient_Tests.swift */; }; /* End PBXBuildFile section */ @@ -223,6 +221,7 @@ 807C5E6829102D9800ECECD8 /* AnalyticsEventData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsEventData.swift; sourceTree = ""; }; 807D56AB2A869044009E591D /* GraphQLHTTPPostBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLHTTPPostBody.swift; sourceTree = ""; }; 807D56AD2A869064009E591D /* GraphQLRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLRequest.swift; sourceTree = ""; }; + 807D56AF2A869F97009E591D /* GraphQLErrorResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLErrorResponse.swift; sourceTree = ""; }; 808EEA80291321FE001B6765 /* AnalyticsEventRequest_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsEventRequest_Tests.swift; sourceTree = ""; }; 80B9F85126B8750000D67843 /* CorePayments.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CorePayments.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 80DB2F752980795D00CFB86A /* CorePaymentsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CorePaymentsError.swift; sourceTree = ""; }; @@ -318,10 +317,7 @@ CBC16DDF29EE2F8200307117 /* PayPalNativePaysheetAction_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalNativePaysheetAction_Tests.swift; sourceTree = ""; }; CBC16DF529EECCB900307117 /* NativeCheckoutProvider_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeCheckoutProvider_Tests.swift; sourceTree = ""; }; CBD6004628D0C24900C3EFF6 /* MockPayPalDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPayPalDelegate.swift; sourceTree = ""; }; - E646231B28369B9B008AC8E1 /* GraphQLQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLQuery.swift; sourceTree = ""; }; E64623222836A69E008AC8E1 /* GraphQLQueryResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLQueryResponse.swift; sourceTree = ""; }; - E64623262836A81E008AC8E1 /* GraphQLError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLError.swift; sourceTree = ""; }; - E64623372836AFC1008AC8E1 /* GraphQLClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLClient.swift; sourceTree = ""; }; E64763702899B60C00074113 /* MockAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAPIClient.swift; sourceTree = ""; }; E699EC15285A388E0044A753 /* GraphQLClient_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLClient_Tests.swift; sourceTree = ""; }; OBJ_16 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -716,12 +712,10 @@ E646231928369B71008AC8E1 /* GraphQL */ = { isa = PBXGroup; children = ( - E64623372836AFC1008AC8E1 /* GraphQLClient.swift */, - E64623262836A81E008AC8E1 /* GraphQLError.swift */, - E646231B28369B9B008AC8E1 /* GraphQLQuery.swift */, E64623222836A69E008AC8E1 /* GraphQLQueryResponse.swift */, 807D56AB2A869044009E591D /* GraphQLHTTPPostBody.swift */, 807D56AD2A869064009E591D /* GraphQLRequest.swift */, + 807D56AF2A869F97009E591D /* GraphQLErrorResponse.swift */, ); path = GraphQL; sourceTree = ""; @@ -1260,11 +1254,9 @@ E6022E802857C6BE008B0E27 /* GraphQLQueryResponse.swift in Sources */, 80E643832A1EBBD2008FD705 /* HTTPResponse.swift in Sources */, 807C5E6929102D9800ECECD8 /* AnalyticsEventData.swift in Sources */, - E6022E822857C6BE008B0E27 /* GraphQLError.swift in Sources */, - E6022E832857C6BE008B0E27 /* GraphQLQuery.swift in Sources */, 80E237DF2A84434B00FF18CA /* HTTPRequest.swift in Sources */, - E6022E842857C6BE008B0E27 /* GraphQLClient.swift in Sources */, 8021B69029144E6D000FBC54 /* PayPalCoreConstants.swift in Sources */, + 807D56B02A869F97009E591D /* GraphQLErrorResponse.swift in Sources */, 80DB2F762980795D00CFB86A /* CorePaymentsError.swift in Sources */, 06CE009926F3D1660000CC46 /* CoreConfig.swift in Sources */, BEA100EC26EFA7790036A6A5 /* HTTPMethod.swift in Sources */, diff --git a/Sources/CorePayments/Networking/GraphQL/GraphQLClient.swift b/Sources/CorePayments/Networking/GraphQL/GraphQLClient.swift deleted file mode 100644 index af51429c0..000000000 --- a/Sources/CorePayments/Networking/GraphQL/GraphQLClient.swift +++ /dev/null @@ -1,58 +0,0 @@ -import Foundation - -class GraphQLClient { - - private let environment: Environment - private let urlSession: URLSessionProtocol - private let jsonDecoder = JSONDecoder() - - public init(environment: Environment, urlSession: URLSessionProtocol = URLSession.shared) { - self.environment = environment - self.urlSession = urlSession - } - - func executeQuery(query: GraphQLQuery) async throws -> GraphQLQueryResponse { - var request = try createURLRequest(requestBody: query.requestBody()) - headers().forEach { key, value in - request.addValue(value, forHTTPHeaderField: key) - } - let (data, response) = try await urlSession.performRequest(with: request) - guard response is HTTPURLResponse else { - return GraphQLQueryResponse(data: nil) - } - let decoded: GraphQLQueryResponse = try parse(data: data) - return decoded - } - - func parse(data: Data) throws -> T { - return try jsonDecoder.decode(T.self, from: data) - } - - func createURLRequest(requestBody: Data) throws -> URLRequest { - var urlRequest = URLRequest(url: environment.graphQLURL) - urlRequest.httpMethod = HTTPMethod.post.rawValue - urlRequest.httpBody = requestBody - return urlRequest - } - - func headers() -> [String: String] { - [ - "Content-type": "application/json", - "Accept": "application/json", - "x-app-name": "northstar", - "Origin": environment.graphQLURL.absoluteString - ] - } -} - -extension GraphQLQuery { - - func requestBody() throws -> Data { - let body: [String: Any] = [ - "query": query, - "variables": variables - ] - let data = try JSONSerialization.data(withJSONObject: body, options: []) - return data - } -} diff --git a/Sources/CorePayments/Networking/GraphQL/GraphQLError.swift b/Sources/CorePayments/Networking/GraphQL/GraphQLError.swift deleted file mode 100644 index 8bc0dcb5f..000000000 --- a/Sources/CorePayments/Networking/GraphQL/GraphQLError.swift +++ /dev/null @@ -1,10 +0,0 @@ -public struct GraphQLError: Codable, Error { - - let message: String - let extensions: [Extension]? -} - -struct Extension: Codable { - - let correlationId: String -} diff --git a/Sources/CorePayments/Networking/GraphQL/GraphQLErrorResponse.swift b/Sources/CorePayments/Networking/GraphQL/GraphQLErrorResponse.swift new file mode 100644 index 000000000..11feb0b4d --- /dev/null +++ b/Sources/CorePayments/Networking/GraphQL/GraphQLErrorResponse.swift @@ -0,0 +1,7 @@ +import Foundation + +struct GraphQLErrorResponse: Decodable { + + let error: String + let correlationId: String? +} diff --git a/Sources/CorePayments/Networking/GraphQL/GraphQLQuery.swift b/Sources/CorePayments/Networking/GraphQL/GraphQLQuery.swift deleted file mode 100644 index ea1c50f2a..000000000 --- a/Sources/CorePayments/Networking/GraphQL/GraphQLQuery.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -protocol GraphQLQuery { - var query: String { get } - var variables: [String: Any] { get } - func requestBody() throws -> Data -} diff --git a/Sources/CorePayments/Networking/HTTPResponseParser.swift b/Sources/CorePayments/Networking/HTTPResponseParser.swift index 8619a6969..058c71bae 100644 --- a/Sources/CorePayments/Networking/HTTPResponseParser.swift +++ b/Sources/CorePayments/Networking/HTTPResponseParser.swift @@ -10,6 +10,7 @@ public class HTTPResponseParser { decoder.keyDecodingStrategy = .convertFromSnakeCase } + // TODO: - Update this func (or file) to handle both GraphQL and REST error parsing public func parse(_ httpResponse: HTTPResponse, as type: T.Type) throws -> T { guard let data = httpResponse.body else { throw APIClientError.noResponseDataError @@ -24,19 +25,11 @@ public class HTTPResponseParser { } } else { do { - let errorData = try decoder.decode(GraphQLErrorResponse.self, from: data) - throw APIClientError.serverResponseError(errorData.error) - -// throw APIClientError.serverResponseError(errorData.readableDescription) + let errorData = try decoder.decode(ErrorResponse.self, from: data) + throw APIClientError.serverResponseError(errorData.readableDescription) } catch { throw APIClientError.jsonDecodingError(error.localizedDescription) } } } } - -struct GraphQLErrorResponse: Decodable { - - let error: String - let correlationId: String? -} From bc00f95d8dc2735a1f4a0cff96bdfa31beb750ad Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 11 Aug 2023 11:57:53 -0500 Subject: [PATCH 21/30] Reset temporary testing setup to CheckoutOrdersAPI.swift --- .../CardPayments/APIRequests/CheckoutOrdersAPI.swift | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift b/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift index e9a30294e..066eea4e3 100644 --- a/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift +++ b/Sources/CardPayments/APIRequests/CheckoutOrdersAPI.swift @@ -16,16 +16,6 @@ class CheckoutOrdersAPI { init(coreConfig: CoreConfig) { self.coreConfig = coreConfig - - let apiClient = APIClient(coreConfig: coreConfig) - Task { - do { - let response = try await apiClient.fetch() - print(response) - } catch { - // - } - } } // MARK: - Internal Methods From 18de2906ebe6eecea4255afdf70a84f9f7b41901 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 11 Aug 2023 12:07:33 -0500 Subject: [PATCH 22/30] Add docstrings to new files --- PayPal.xcodeproj/project.pbxproj | 8 ++++---- .../Networking/GraphQL/GraphQLErrorResponse.swift | 1 + .../Networking/GraphQL/GraphQLHTTPResponse.swift | 5 +++++ .../Networking/GraphQL/GraphQLQueryResponse.swift | 4 ---- .../CorePayments/Networking/GraphQL/GraphQLRequest.swift | 9 ++++++--- Sources/CorePayments/Networking/HTTPRequest.swift | 2 +- 6 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 Sources/CorePayments/Networking/GraphQL/GraphQLHTTPResponse.swift delete mode 100644 Sources/CorePayments/Networking/GraphQL/GraphQLQueryResponse.swift diff --git a/PayPal.xcodeproj/project.pbxproj b/PayPal.xcodeproj/project.pbxproj index 6a7834f93..77d8900e2 100644 --- a/PayPal.xcodeproj/project.pbxproj +++ b/PayPal.xcodeproj/project.pbxproj @@ -137,7 +137,7 @@ CBC16DE029EE2F8200307117 /* PayPalNativePaysheetAction_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC16DDF29EE2F8200307117 /* PayPalNativePaysheetAction_Tests.swift */; }; CBC16DF629EECCB900307117 /* NativeCheckoutProvider_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC16DF529EECCB900307117 /* NativeCheckoutProvider_Tests.swift */; }; CBD6004728D0C24A00C3EFF6 /* MockPayPalDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD6004628D0C24900C3EFF6 /* MockPayPalDelegate.swift */; }; - E6022E802857C6BE008B0E27 /* GraphQLQueryResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64623222836A69E008AC8E1 /* GraphQLQueryResponse.swift */; }; + E6022E802857C6BE008B0E27 /* GraphQLHTTPResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64623222836A69E008AC8E1 /* GraphQLHTTPResponse.swift */; }; E64763712899B60C00074113 /* MockAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64763702899B60C00074113 /* MockAPIClient.swift */; }; E699EC16285A388E0044A753 /* GraphQLClient_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E699EC15285A388E0044A753 /* GraphQLClient_Tests.swift */; }; /* End PBXBuildFile section */ @@ -317,7 +317,7 @@ CBC16DDF29EE2F8200307117 /* PayPalNativePaysheetAction_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayPalNativePaysheetAction_Tests.swift; sourceTree = ""; }; CBC16DF529EECCB900307117 /* NativeCheckoutProvider_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeCheckoutProvider_Tests.swift; sourceTree = ""; }; CBD6004628D0C24900C3EFF6 /* MockPayPalDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPayPalDelegate.swift; sourceTree = ""; }; - E64623222836A69E008AC8E1 /* GraphQLQueryResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLQueryResponse.swift; sourceTree = ""; }; + E64623222836A69E008AC8E1 /* GraphQLHTTPResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLHTTPResponse.swift; sourceTree = ""; }; E64763702899B60C00074113 /* MockAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAPIClient.swift; sourceTree = ""; }; E699EC15285A388E0044A753 /* GraphQLClient_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLClient_Tests.swift; sourceTree = ""; }; OBJ_16 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -712,7 +712,7 @@ E646231928369B71008AC8E1 /* GraphQL */ = { isa = PBXGroup; children = ( - E64623222836A69E008AC8E1 /* GraphQLQueryResponse.swift */, + E64623222836A69E008AC8E1 /* GraphQLHTTPResponse.swift */, 807D56AB2A869044009E591D /* GraphQLHTTPPostBody.swift */, 807D56AD2A869064009E591D /* GraphQLRequest.swift */, 807D56AF2A869F97009E591D /* GraphQLErrorResponse.swift */, @@ -1251,7 +1251,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E6022E802857C6BE008B0E27 /* GraphQLQueryResponse.swift in Sources */, + E6022E802857C6BE008B0E27 /* GraphQLHTTPResponse.swift in Sources */, 80E643832A1EBBD2008FD705 /* HTTPResponse.swift in Sources */, 807C5E6929102D9800ECECD8 /* AnalyticsEventData.swift in Sources */, 80E237DF2A84434B00FF18CA /* HTTPRequest.swift in Sources */, diff --git a/Sources/CorePayments/Networking/GraphQL/GraphQLErrorResponse.swift b/Sources/CorePayments/Networking/GraphQL/GraphQLErrorResponse.swift index 11feb0b4d..6fd6ed4ab 100644 --- a/Sources/CorePayments/Networking/GraphQL/GraphQLErrorResponse.swift +++ b/Sources/CorePayments/Networking/GraphQL/GraphQLErrorResponse.swift @@ -1,5 +1,6 @@ import Foundation +/// Used to parse error message details out of GraphQL HTTP response body struct GraphQLErrorResponse: Decodable { let error: String diff --git a/Sources/CorePayments/Networking/GraphQL/GraphQLHTTPResponse.swift b/Sources/CorePayments/Networking/GraphQL/GraphQLHTTPResponse.swift new file mode 100644 index 000000000..6497122e6 --- /dev/null +++ b/Sources/CorePayments/Networking/GraphQL/GraphQLHTTPResponse.swift @@ -0,0 +1,5 @@ +/// Used to decode the HTTP reponse body of GraphQL requests +struct GraphQLHTTPResponse: Codable { + + let data: T? +} diff --git a/Sources/CorePayments/Networking/GraphQL/GraphQLQueryResponse.swift b/Sources/CorePayments/Networking/GraphQL/GraphQLQueryResponse.swift deleted file mode 100644 index 6f29f25ff..000000000 --- a/Sources/CorePayments/Networking/GraphQL/GraphQLQueryResponse.swift +++ /dev/null @@ -1,4 +0,0 @@ -struct GraphQLQueryResponse: Codable { - - let data: T? -} diff --git a/Sources/CorePayments/Networking/GraphQL/GraphQLRequest.swift b/Sources/CorePayments/Networking/GraphQL/GraphQLRequest.swift index 45b53bf0d..6731cfd64 100644 --- a/Sources/CorePayments/Networking/GraphQL/GraphQLRequest.swift +++ b/Sources/CorePayments/Networking/GraphQL/GraphQLRequest.swift @@ -1,9 +1,12 @@ import Foundation -/// :nodoc: +/// :nodoc: Values needed to initiate a GraphQL network request public struct GraphQLRequest { - let queryNameForURL: String? let query: String - let variables: Data // Dictionary + let variables: Data + + /// This is non-standard in the GraphQL language, but sometimes required by PayPal's GraphQL API. + /// Some requests are sent to `https://www.api.paypal.com/graphql?` + let queryNameForURL: String? } diff --git a/Sources/CorePayments/Networking/HTTPRequest.swift b/Sources/CorePayments/Networking/HTTPRequest.swift index 1f8862fa4..ddf67ed4d 100644 --- a/Sources/CorePayments/Networking/HTTPRequest.swift +++ b/Sources/CorePayments/Networking/HTTPRequest.swift @@ -1,6 +1,6 @@ import Foundation -/// :nodoc: +/// :nodoc: Values needed to initiate a REST network request public struct HTTPRequest { let headers: [HTTPHeader: String] From 2365fc5177fb1bc746a7cd6f8d1f3eca9adbf385 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 11 Aug 2023 12:10:38 -0500 Subject: [PATCH 23/30] Leave TODO for renaming APIClient --> Networking --- Sources/CorePayments/Networking/APIClient.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index ec1ac1bd7..6687f9934 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -3,6 +3,7 @@ import Foundation /// :nodoc: This method is exposed for internal PayPal use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time. /// /// `APIClient` is the entry point for each payment method feature to perform API requests. It also offers convenience methods for API requests used across multiple payment methods / modules. +// TODO: - Rename to `NetworkingClient`. Now that we have `API.swift` classes, ths responsibility of this class really is to coordinate networking. public class APIClient { // MARK: - Internal Properties From 2d1bcc07b33f46334e53a940d82e9c70cb89e7a0 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 11 Aug 2023 12:27:02 -0500 Subject: [PATCH 24/30] Fixup - add more detail to APIClient rename todo --- Sources/CorePayments/Networking/APIClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index 6687f9934..5331299fe 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -1,9 +1,9 @@ import Foundation +// TODO: - Rename to `NetworkingClient`. Now that we have `API.swift` classes, ths responsibility of this class really is to coordinate networking. It transforms REST & GraphQL into HTTP requests. /// :nodoc: This method is exposed for internal PayPal use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time. /// /// `APIClient` is the entry point for each payment method feature to perform API requests. It also offers convenience methods for API requests used across multiple payment methods / modules. -// TODO: - Rename to `NetworkingClient`. Now that we have `API.swift` classes, ths responsibility of this class really is to coordinate networking. public class APIClient { // MARK: - Internal Properties From 0695f72aacbd29c01ac0c2c471f8b88245d02ea8 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Wed, 16 Aug 2023 10:14:01 -0500 Subject: [PATCH 25/30] Fixup - reorder & rename constructURL methods in APIClient --- .../CorePayments/Networking/APIClient.swift | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index 9678be6f1..88680b7d9 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -30,7 +30,7 @@ public class APIClient { /// :nodoc: public func fetch(request: RESTRequest) async throws -> HTTPResponse { - let url = try constructURL(path: request.path, queryParameters: request.queryParameters ?? [:]) + let url = try constructRestURL(path: request.path, queryParameters: request.queryParameters ?? [:]) let base64EncodedCredentials = Data(coreConfig.clientID.appending(":").utf8).base64EncodedString() @@ -71,19 +71,7 @@ public class APIClient { // MARK: - Private Methods - private func constructGraphQLURL(queryName: String? = nil) throws -> URL { - guard let queryName else { - return coreConfig.environment.graphQLURL - } - - if let url = URL(string: coreConfig.environment.graphQLURL.absoluteString + "?" + queryName) { - return url - } else { - throw CorePaymentsError.urlEncodingFailed - } - } - - private func constructURL(path: String, queryParameters: [String: String]) throws -> URL { + private func constructRestURL(path: String, queryParameters: [String: String]) throws -> URL { let urlString = coreConfig.environment.baseURL.appendingPathComponent(path) var urlComponents = URLComponents(url: urlString, resolvingAgainstBaseURL: false) @@ -97,4 +85,16 @@ public class APIClient { return url } + + private func constructGraphQLURL(queryName: String? = nil) throws -> URL { + guard let queryName else { + return coreConfig.environment.graphQLURL + } + + if let url = URL(string: coreConfig.environment.graphQLURL.absoluteString + "?" + queryName) { + return url + } else { + throw CorePaymentsError.urlEncodingFailed + } + } } From 2d5a36a4e3bb0ce8031c7a298cab85d5f73e8bf7 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Wed, 16 Aug 2023 14:38:58 -0500 Subject: [PATCH 26/30] PR Feedback - prefer guard over if-let --- Sources/CorePayments/Networking/APIClient.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index 88680b7d9..7a40ac61f 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -91,10 +91,10 @@ public class APIClient { return coreConfig.environment.graphQLURL } - if let url = URL(string: coreConfig.environment.graphQLURL.absoluteString + "?" + queryName) { - return url - } else { + guard let url = URL(string: coreConfig.environment.graphQLURL.absoluteString + "?" + queryName) else { throw CorePaymentsError.urlEncodingFailed } + + return url } } From 4ef956b78d8340d6eff9894965817d81e3be1482 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Wed, 16 Aug 2023 14:44:01 -0500 Subject: [PATCH 27/30] PR Feedback - use custom coding keys for GraphQLErrorResponse --- .../Networking/GraphQL/GraphQLErrorResponse.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/CorePayments/Networking/GraphQL/GraphQLErrorResponse.swift b/Sources/CorePayments/Networking/GraphQL/GraphQLErrorResponse.swift index 6fd6ed4ab..8205f98bc 100644 --- a/Sources/CorePayments/Networking/GraphQL/GraphQLErrorResponse.swift +++ b/Sources/CorePayments/Networking/GraphQL/GraphQLErrorResponse.swift @@ -3,6 +3,11 @@ import Foundation /// Used to parse error message details out of GraphQL HTTP response body struct GraphQLErrorResponse: Decodable { + enum CodingKeys: String, CodingKey { + case error = "error" + case correlationID = "correlation_id" + } + let error: String - let correlationId: String? + let correlationID: String? } From d3fe18806d8df1cd523662e998ecb2ff46263ba3 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Thu, 17 Aug 2023 10:15:42 -0500 Subject: [PATCH 28/30] PR Feedback - rename constructRestURL -> constructRESTURL method --- Sources/CorePayments/Networking/APIClient.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index 7a40ac61f..f4192e65b 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -30,7 +30,7 @@ public class APIClient { /// :nodoc: public func fetch(request: RESTRequest) async throws -> HTTPResponse { - let url = try constructRestURL(path: request.path, queryParameters: request.queryParameters ?? [:]) + let url = try constructRESTURL(path: request.path, queryParameters: request.queryParameters ?? [:]) let base64EncodedCredentials = Data(coreConfig.clientID.appending(":").utf8).base64EncodedString() @@ -71,7 +71,7 @@ public class APIClient { // MARK: - Private Methods - private func constructRestURL(path: String, queryParameters: [String: String]) throws -> URL { + private func constructRESTURL(path: String, queryParameters: [String: String]) throws -> URL { let urlString = coreConfig.environment.baseURL.appendingPathComponent(path) var urlComponents = URLComponents(url: urlString, resolvingAgainstBaseURL: false) From ff3ed9b993134432f620d30c407e34e570f51e4c Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Thu, 17 Aug 2023 10:19:23 -0500 Subject: [PATCH 29/30] PR Feedback - use single line for HTTPRequest() instantiation --- Sources/CorePayments/Networking/APIClient.swift | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index f4192e65b..b6592d9aa 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -42,12 +42,7 @@ public class APIClient { headers[.contentType] = "application/json" } - let httpRequest = HTTPRequest( - headers: headers, - method: request.method, - url: url, - body: request.body - ) + let httpRequest = HTTPRequest(headers: headers, method: request.method, url: url, body: request.body) return try await http.performRequest(httpRequest) } @@ -59,12 +54,7 @@ public class APIClient { let postBody = GraphQLHTTPPostBody(query: request.query, variables: request.variables) let postData = try JSONEncoder().encode(postBody) - let httpRequest = HTTPRequest( - headers: [.contentType: "application/json"], - method: .post, - url: url, - body: postData - ) + let httpRequest = HTTPRequest(headers: [.contentType: "application/json"], method: .post, url: url, body: postData) return try await http.performRequest(httpRequest) } From 7afefa2939e6135df70439625596fd6c085244f2 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Thu, 17 Aug 2023 14:00:58 -0500 Subject: [PATCH 30/30] PR Feedback - consolidate variables in APIClient.fetch(restRequest) where possible --- Sources/CorePayments/Networking/APIClient.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/CorePayments/Networking/APIClient.swift b/Sources/CorePayments/Networking/APIClient.swift index b6592d9aa..444dcadaf 100644 --- a/Sources/CorePayments/Networking/APIClient.swift +++ b/Sources/CorePayments/Networking/APIClient.swift @@ -33,11 +33,9 @@ public class APIClient { let url = try constructRESTURL(path: request.path, queryParameters: request.queryParameters ?? [:]) let base64EncodedCredentials = Data(coreConfig.clientID.appending(":").utf8).base64EncodedString() - var headers: [HTTPHeader: String] = [ .authorization: "Basic \(base64EncodedCredentials)" ] - if request.method == .post { headers[.contentType] = "application/json" }