Skip to content

Commit

Permalink
Eligibility API logic (#286)
Browse files Browse the repository at this point in the history
* Add EligibilityRequest file

* Add EligibilityResult file

* Add EligibilityClient file

* Add EligilibilityClient method

* Add Result properties

* Add request properties

* Add EligibilityVariables file

* Remove header

* Add EligibilityAPI

* Sort directory

* Add properties and initializers

* Add tests files

* Fix lints

* Fix lint

* PR Feedback

* Conform Encodable and add variable eligibility variables properties

* Add supported currency type enum

* Add eligibility intent enum

* Add eligibility result properties

* Add Eligibility Response object

* Sort files Eligibility directory

* Update eligibility request with enum types

* Add parameters and initializer on Client

* Add API logic

* Add mock and remove intent enum

* Remove final

* Add docstrings on Client

* Expose intents

* Change Decodable instead of Codable for response model

* Update Variables struct

* Remove currency enum

* Add docstring for Request model

* Add mock API

* Add client tests

* Add Result docstrings

* Rename curency to currencyCode

* Add marks

* Fix merge conflicts

* Fix lint

* Add mising file

* Add functionality on demo app

* PR feedback

* Change initializer

* Fix tests

* Fix coding key

* Add EligibilityIntent docstrings and remove unnecessary intents

* Fix UTs

* Pass intent as parameter and mimic UI as others views

* Change initializer

* Remove Recovered References directory

* Lowercase supported payment methods

* Use CurrentState enum instead of VenmoState

* Remove VenmoState file

* Update Sources/CorePayments/Eligibility/EligibilityAPI.swift

Co-authored-by: Jax DesMarais-Leder <[email protected]>

---------

Co-authored-by: Jax DesMarais-Leder <[email protected]>
  • Loading branch information
richherrera and jaxdesmarais authored Jul 18, 2024
1 parent 2cced79 commit c281aa9
Show file tree
Hide file tree
Showing 14 changed files with 543 additions and 40 deletions.
42 changes: 41 additions & 1 deletion Demo/Demo/SwiftUIComponents/VenmoPayments/VenmoPaymentView.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,51 @@
import SwiftUI
import CorePayments

struct VenmoPaymentView: View {

@State private var selectedIntent: EligibilityIntent = .capture
@StateObject var venmoPaymentsViewModel = VenmoPaymentsViewModel()

var body: some View {
Text("Hello, Venmo!")
VStack(spacing: 16) {
Text("Hello, Venmo!")
Picker("Intent", selection: $selectedIntent) {
Text("AUTHORIZE").tag(EligibilityIntent.authorize)
Text("CAPTURE").tag(EligibilityIntent.capture)
}
.pickerStyle(SegmentedPickerStyle())
ZStack {
Button("Check eligibility") {
Task {
do {
try await venmoPaymentsViewModel.getEligibility(selectedIntent)
} catch {
print("Error in getting payment token. \(error.localizedDescription)")
}
}
}
.buttonStyle(RoundedBlueButtonStyle())
if case .loading = venmoPaymentsViewModel.state {
CircularProgressView()
}
}
if case .success = venmoPaymentsViewModel.state {
if venmoPaymentsViewModel.isVenmoEligible {
Text("Venmo is eligible! 🥳")
} else {
Text("Venmo is not eligible! 🫤")
}
}
if case .error(let message) = venmoPaymentsViewModel.state {
Text(message)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.stroke(.gray, lineWidth: 2)
.padding(5)
)
}
}

Expand Down
35 changes: 34 additions & 1 deletion Demo/Demo/ViewModels/VenmoPaymentsViewModel.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
import CorePayments
import Foundation

class VenmoPaymentsViewModel: ObservableObject { }
class VenmoPaymentsViewModel: ObservableObject {

let configManager = CoreConfigManager(domain: "Venmo Payments")

@Published var state: CurrentState = .idle

private var eligibilityResult: EligibilityResult?

var isVenmoEligible: Bool {
eligibilityResult?.isVenmoEligible ?? false
}

func getEligibility(_ intent: EligibilityIntent) async throws {
DispatchQueue.main.async {
self.state = .loading
}
do {
let config = try await configManager.getCoreConfig()
let eligibilityRequest = EligibilityRequest(currencyCode: "USD", intent: intent)
let eligibilityClient = EligibilityClient(config: config)
eligibilityResult = try? await eligibilityClient.check(eligibilityRequest)

DispatchQueue.main.async {
self.state = .success
}
} catch {
DispatchQueue.main.async {
self.state = .error(message: error.localizedDescription)
}
print("failed in updating setup token. \(error.localizedDescription)")
}
}
}
50 changes: 33 additions & 17 deletions PayPal.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@
3BE7386D2B9A670400598F05 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3BE738662B9A593100598F05 /* PrivacyInfo.xcprivacy */; };
3D1763A22720722A00652E1C /* CardResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1763A12720722A00652E1C /* CardResult.swift */; };
3DC42BA927187E8300B71645 /* ErrorResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC42BA827187E8300B71645 /* ErrorResponse.swift */; };
459633162C46BD63002008EF /* EligibilityIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459633152C46BD63002008EF /* EligibilityIntent.swift */; };
459633182C46BD6F002008EF /* EligibilityResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459633172C46BD6F002008EF /* EligibilityResponse.swift */; };
4596331A2C46BD7B002008EF /* SupportedPaymentMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459633192C46BD7B002008EF /* SupportedPaymentMethods.swift */; };
45B063B42C4035E200E743F2 /* EligibilityClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B063B22C4035DB00E743F2 /* EligibilityClient.swift */; };
45B063B52C4035E500E743F2 /* EligibilityResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B063B02C4034B700E743F2 /* EligibilityResult.swift */; };
45B063B62C4035EA00E743F2 /* EligibilityRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B063AE2C40349300E743F2 /* EligibilityRequest.swift */; };
45B063B82C40440000E743F2 /* EligibilityVariables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B063B72C40440000E743F2 /* EligibilityVariables.swift */; };
45B063BA2C40456F00E743F2 /* EligibilityAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B063B92C40456F00E743F2 /* EligibilityAPI.swift */; };
45B063BD2C40545100E743F2 /* EligibilityClient_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B063BC2C40545100E743F2 /* EligibilityClient_Tests.swift */; };
45B063BF2C40549000E743F2 /* EligibilityAPI_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B063BE2C40549000E743F2 /* EligibilityAPI_Tests.swift */; };
45B063CA2C459F9900E743F2 /* MockEligibilityAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B063C92C459F9900E743F2 /* MockEligibilityAPI.swift */; };
53A2A4E228A182AC0093441C /* NativeCheckoutProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A2A4E128A182AC0093441C /* NativeCheckoutProvider.swift */; };
62D3FB292C3DB5130046563B /* CorePayments.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 80B9F85126B8750000D67843 /* CorePayments.framework */; platformFilter = ios; };
62D3FB4B2C3ED82D0046563B /* VenmoClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D3FB492C3ED82D0046563B /* VenmoClient.swift */; };
Expand Down Expand Up @@ -253,13 +257,17 @@
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 = "<group>"; };
3DC42BA827187E8300B71645 /* ErrorResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorResponse.swift; sourceTree = "<group>"; };
459633152C46BD63002008EF /* EligibilityIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EligibilityIntent.swift; sourceTree = "<group>"; };
459633172C46BD6F002008EF /* EligibilityResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EligibilityResponse.swift; sourceTree = "<group>"; };
459633192C46BD7B002008EF /* SupportedPaymentMethods.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SupportedPaymentMethods.swift; sourceTree = "<group>"; };
45B063AE2C40349300E743F2 /* EligibilityRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EligibilityRequest.swift; sourceTree = "<group>"; };
45B063B02C4034B700E743F2 /* EligibilityResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EligibilityResult.swift; sourceTree = "<group>"; };
45B063B22C4035DB00E743F2 /* EligibilityClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EligibilityClient.swift; sourceTree = "<group>"; };
45B063B72C40440000E743F2 /* EligibilityVariables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EligibilityVariables.swift; sourceTree = "<group>"; };
45B063B92C40456F00E743F2 /* EligibilityAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EligibilityAPI.swift; sourceTree = "<group>"; };
45B063BC2C40545100E743F2 /* EligibilityClient_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EligibilityClient_Tests.swift; sourceTree = "<group>"; };
45B063BE2C40549000E743F2 /* EligibilityAPI_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EligibilityAPI_Tests.swift; sourceTree = "<group>"; };
45B063C92C459F9900E743F2 /* MockEligibilityAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockEligibilityAPI.swift; sourceTree = "<group>"; };
53A2A4E128A182AC0093441C /* NativeCheckoutProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeCheckoutProvider.swift; sourceTree = "<group>"; };
62D3FB132C3DB4D40046563B /* VenmoClient_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VenmoClient_Tests.swift; sourceTree = "<group>"; };
62D3FB2F2C3DB5130046563B /* VenmoPayments.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = VenmoPayments.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -570,31 +578,17 @@
path = Models;
sourceTree = "<group>";
};
62D3FB122C3DB4B30046563B /* VenmoPaymentsTests */ = {
isa = PBXGroup;
children = (
62D3FB132C3DB4D40046563B /* VenmoClient_Tests.swift */,
);
path = VenmoPaymentsTests;
sourceTree = "<group>";
};
62D3FB4A2C3ED82D0046563B /* VenmoPayments */ = {
isa = PBXGroup;
children = (
62D3FB492C3ED82D0046563B /* VenmoClient.swift */,
);
name = VenmoPayments;
path = Sources/VenmoPayments;
sourceTree = "<group>";
};
45B063AD2C40346500E743F2 /* Eligibility */ = {
isa = PBXGroup;
children = (
45B063B92C40456F00E743F2 /* EligibilityAPI.swift */,
45B063B22C4035DB00E743F2 /* EligibilityClient.swift */,
459633152C46BD63002008EF /* EligibilityIntent.swift */,
45B063AE2C40349300E743F2 /* EligibilityRequest.swift */,
459633172C46BD6F002008EF /* EligibilityResponse.swift */,
45B063B02C4034B700E743F2 /* EligibilityResult.swift */,
45B063B72C40440000E743F2 /* EligibilityVariables.swift */,
459633192C46BD7B002008EF /* SupportedPaymentMethods.swift */,
);
path = Eligibility;
sourceTree = "<group>";
Expand All @@ -608,6 +602,23 @@
path = Eligibility;
sourceTree = "<group>";
};
62D3FB122C3DB4B30046563B /* VenmoPaymentsTests */ = {
isa = PBXGroup;
children = (
62D3FB132C3DB4D40046563B /* VenmoClient_Tests.swift */,
);
path = VenmoPaymentsTests;
sourceTree = "<group>";
};
62D3FB4A2C3ED82D0046563B /* VenmoPayments */ = {
isa = PBXGroup;
children = (
62D3FB492C3ED82D0046563B /* VenmoClient.swift */,
);
name = VenmoPayments;
path = Sources/VenmoPayments;
sourceTree = "<group>";
};
8036C1DE270F9BCF00C0F091 /* PaymentsCoreTests */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -650,6 +661,7 @@
isa = PBXGroup;
children = (
802EFBD72A9685DF00AB709D /* MockTrackingEventsAPI.swift */,
45B063C92C459F9900E743F2 /* MockEligibilityAPI.swift */,
);
path = Mocks;
sourceTree = "<group>";
Expand Down Expand Up @@ -1482,6 +1494,7 @@
files = (
808EEA81291321FE001B6765 /* AnalyticsEventData_Tests.swift in Sources */,
8036C1E5270F9BE700C0F091 /* Environment_Tests.swift in Sources */,
45B063CA2C459F9900E743F2 /* MockEligibilityAPI.swift in Sources */,
80B96AAE2A980F6B00C62916 /* MockTrackingEventsAPI.swift in Sources */,
80FC261D29847AC7008EC841 /* HTTP_Tests.swift in Sources */,
45B063BD2C40545100E743F2 /* EligibilityClient_Tests.swift in Sources */,
Expand All @@ -1498,6 +1511,7 @@
buildActionMask = 2147483647;
files = (
E6022E802857C6BE008B0E27 /* GraphQLHTTPResponse.swift in Sources */,
459633182C46BD6F002008EF /* EligibilityResponse.swift in Sources */,
80E643832A1EBBD2008FD705 /* HTTPResponse.swift in Sources */,
807C5E6929102D9800ECECD8 /* AnalyticsEventData.swift in Sources */,
80E237DF2A84434B00FF18CA /* HTTPRequest.swift in Sources */,
Expand All @@ -1514,6 +1528,7 @@
80E2FDC12A83535A0045593D /* TrackingEventsAPI.swift in Sources */,
BEA100F226EFA7DE0036A6A5 /* Environment.swift in Sources */,
BEA100F026EFA7C20036A6A5 /* NetworkingClientError.swift in Sources */,
459633162C46BD63002008EF /* EligibilityIntent.swift in Sources */,
804E628629380B04004B9FEF /* AnalyticsService.swift in Sources */,
45B063BA2C40456F00E743F2 /* EligibilityAPI.swift in Sources */,
BC04837427B2FC7300FA7B46 /* URLSession+URLSessionProtocol.swift in Sources */,
Expand All @@ -1524,6 +1539,7 @@
804E62822937EBCE004B9FEF /* HTTP.swift in Sources */,
BC04836F27B2FB3600FA7B46 /* URLSessionProtocol.swift in Sources */,
065A4DBC26FCD8090007014A /* CoreSDKError.swift in Sources */,
4596331A2C46BD7B002008EF /* SupportedPaymentMethods.swift in Sources */,
BEA100E726EF9EDA0036A6A5 /* NetworkingClient.swift in Sources */,
807BF58F2A2A5D19002F32B3 /* HTTPResponseParser.swift in Sources */,
807D56AE2A869064009E591D /* GraphQLRequest.swift in Sources */,
Expand Down
65 changes: 63 additions & 2 deletions Sources/CorePayments/Eligibility/EligibilityAPI.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,77 @@
import Foundation

final class EligibilityAPI {
/// API that return merchant's eligibility for different payment methods: Venmo, PayPal, PayPal Credit, Pay Later & credit card
class EligibilityAPI {

// MARK: - Private Propertires

private let coreConfig: CoreConfig
private let networkingClient: NetworkingClient

// MARK: - Initializer
// MARK: - Initializers

/// Initialize the eligibility API to check for payment methods eligibility
/// - Parameter coreConfig: configuration object
init(coreConfig: CoreConfig) {
self.coreConfig = coreConfig
self.networkingClient = NetworkingClient(coreConfig: coreConfig)
}

/// Exposed for injecting MockNetworkingClient in tests
init(coreConfig: CoreConfig, networkingClient: NetworkingClient) {
self.coreConfig = coreConfig
self.networkingClient = networkingClient
}

// MARK: - Internal Methods

/// Checks merchants eligibility for different payment methods.
/// - Returns: An `EligibilityResponse` containing the result of the eligibility check.
/// - Throws: An `Error` describing the failure.

func check(_ eligibilityRequest: EligibilityRequest) async throws -> EligibilityResponse {
let variables = EligibilityVariables(eligibilityRequest: eligibilityRequest, clientID: coreConfig.clientID)
let graphQLRequest = GraphQLRequest(query: Self.rawQuery, variables: variables)
let httpResponse = try await networkingClient.fetch(request: graphQLRequest)

return try HTTPResponseParser().parseGraphQL(httpResponse, as: EligibilityResponse.self)
}
}

extension EligibilityAPI {

static 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
}
}
}
"""
}
31 changes: 29 additions & 2 deletions Sources/CorePayments/Eligibility/EligibilityClient.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,36 @@
import Foundation

/// The `EligibilityClient` class provides methods to check eligibility status based on provided requests.
public final class EligibilityClient {

private let api: EligibilityAPI
private let config: CoreConfig

// MARK: - Initializers

/// Initializes a new instance of `EligibilityClient`.
/// - Parameter config: The core configuration needed for the client.
public init(config: CoreConfig) {
self.config = config
self.api = EligibilityAPI(coreConfig: config)
}

/// Exposed for injecting MockEligibilityAPI in tests
init(config: CoreConfig, api: EligibilityAPI) {
self.config = config
self.api = api
}

// MARK: - Public Methods

/// Checks the eligibility based on the provided `EligibilityRequest`.
///
/// This method calls the `EligibilityAPI` to perform the check and converts the response to `EligibilityResult`.
///
/// - Parameter eligibilityRequest: The eligibility request containing the necessary data to perform the check.
/// - Throws: An error if the network request or parsing fails.
/// - Returns: An `EligibilityResult` containing the result of the eligibility check.
public func check(_ eligibilityRequest: EligibilityRequest) async throws -> EligibilityResult {
// TODO: - Add logic
.init(isVenmoEligible: false)
try await api.check(eligibilityRequest).asResult
}
}
11 changes: 11 additions & 0 deletions Sources/CorePayments/Eligibility/EligibilityIntent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

/// Enum representing the possible intents for eligibility.
public enum EligibilityIntent: String {

/// Represents the intent to capture a payment.
case capture = "CAPTURE"

/// Represents the intent to authorize a payment.
case authorize = "AUTHORIZE"
}
24 changes: 18 additions & 6 deletions Sources/CorePayments/Eligibility/EligibilityRequest.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import Foundation

/// The `EligibilityRequest` structure includes the necessary parameters to make an eligibility check request.
public struct EligibilityRequest {

let intent: String
let currency: String
let enableFunding: [String]
// MARK: - Internal Properties

/// The currency code for the eligibility request.
let currencyCode: String

/// The intent of the eligibility request.
let intent: EligibilityIntent

/// An array of supported payment methods for the request. Defaults to `[.VENMO]`.
let enableFunding: [SupportedPaymentMethodsType]

// MARK: - Initializer

public init(intent: String, currency: String, enableFunding: [String]) {
/// Creates an instance of a eligibility request
/// - Parameters:
/// - currencyCode: The currency code for the eligibility request.
/// - intent: The intent of the eligibility request.
public init(currencyCode: String, intent: EligibilityIntent) {
self.currencyCode = currencyCode
self.intent = intent
self.currency = currency
self.enableFunding = enableFunding
self.enableFunding = [.venmo]
}
}
Loading

0 comments on commit c281aa9

Please sign in to comment.