-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vault Without Purchase PayPal #225
Conversation
@@ -12,6 +12,7 @@ struct CardVaultState: Equatable { | |||
var setupToken: SetUpTokenResponse? | |||
var updateSetupToken: UpdateSetupTokenResult? | |||
var paymentToken: PaymentTokenResponse? | |||
var paypalVaultToken: String? | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering if I should add cancel state in LoadingState.
I do provide delegate function for cancel so merchant can handle it the way they want
but I didn't include it as a separate state in the demo app because it didn't make sense for me to
display anything for canceling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it depends on the Merchant's needs. For now, we can leave it as is; It LGTM. In fact, if you'd like, we can even consider the approach used in the func cardDidCancel(_ cardClient: CardPayments.CardClient)
within the CardPaymentViewModel
, where we use the .idle state.
@@ -21,6 +22,14 @@ struct CardVaultState: Equatable { | |||
} | |||
} | |||
|
|||
var paypalVaultTokenResponse: LoadingState<String> = .idle { | |||
didSet { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I prefer the separate variables of loading states for each API response.
This way, I can have a result view to toggle errorView and successView.
I prefer separated result views here instead of toggling generic one with different options from the
container view, especially with just a few different result views here that are shared across different payment methods.
I've been thinking about the approach to minimize the number of views in the demo app as suggested in previous PR's such as suggestion to use general view that toggles content for each API result and approach that I explored above, which was to use parent viewModel between two payment methods for vaulting without payment so that I considered other payment methods being added as stand alone vault features in this approach to reducing the number of views by having a parent VaultViewModel class with shared state and payment specific properties as published properties in their respective viewModels. In JS, so far we have PayPal and Card vaulting and in Braintree, we have PayPal, Card and Venmo vaulting if I remember correctly. Assuming that purpose of our demo app is for internal testing, demonstration of features and giving example usage of SDK to merchants, I think simplicity might be the best approach. We can adapt as we add more payment methods to vault. As far as I know, the flow for vault without pay is the same: create setupToken, do approval in SDK with payment method and merchants use this temporary setupToken to create paymentToken. |
Co-authored-by: Ricardo Herrera <<[email protected]>
class PayPalVaultViewModel: VaultViewModel, PayPalVaultDelegate { | ||
|
||
@Published var paypalVaultToken: PayPalVaultResult? | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered having same VaultResult
for card and PayPal vaulting but UpdateSetupToken
endpoint
for card vaulting returns a lot more values than url string from PayPal approval web session, so I opted to keep them separate in case there is request in future to return more values to merchant.
If only minimum values that I display here are needed, they can be consolidated into a single VaultResult
and VaultView
and included in the VaultState
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my perspective, communicating the viewModel with the view without using the State
structure somewhat breaks the paradigm. For now, I think it's better to move PayPalVaultViewModel.paypalVaultTokenResponse
and CardVaultViewModel.updateSetupTokenResponse
back to VaultState
, as it was in previous commits. Maybe someone else on the team could provide some input. Considering that the purpose of the Demo app is all about internal testing, showcase functionalities, and providing merchants with examples of SDK usage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I thought I might get comment that it's confusing to be able to access updateSetupToken from PayPalVaultViewModel but I prefer them all in vaultState as well.
@@ -28,6 +28,10 @@ struct SetupTokenResultView: View { | |||
LeadingText("\(setupTokenResponse.customer?.id ?? "")") | |||
LeadingText("Status", weight: .bold) | |||
LeadingText("\(setupTokenResponse.status)") | |||
if let url = setupTokenResponse.paypalURL { | |||
LeadingText("PayPalURL", weight: .bold) | |||
LeadingText("\(setupTokenResponse.paypalURL ?? "No url")") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LeadingText("\(setupTokenResponse.paypalURL ?? "No url")") | |
LeadingText(url) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for catching that.
|
||
struct CardVaultState: Equatable { | ||
struct VaultState: Equatable { | ||
|
||
struct UpdateSetupTokenResult: Decodable, Equatable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This structure is duplicated on CardVaultViewModel
. I see that we don't have any rules for handling nested structures in the States structures. For now, I think we should just define which one to work with
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that was a mistake leaving it there. Good catch.
) | ||
SetupTokenResultView(cardVaultViewModel: cardVaultViewModel) | ||
SetupTokenResultView(vaultViewModel: cardVaultViewModel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: How about we just use viewModel
instead of specifying which viewModel it is (cardVaultViewModel or vaulViewModel)? It's most convenient for a view to have only one ViewModel, so it's not really necessary to specify the type of ViewModel in the parameter
SetupTokenResultView(vaultViewModel: cardVaultViewModel) | |
SetupTokenResultView(viewModel: cardVaultViewModel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's true, we are only dealing with vaultViewModels here, so it's redundant.
|
||
class PayPalVaultViewModel: VaultViewModel, PayPalVaultDelegate { | ||
|
||
@Published var paypalVaultToken: PayPalVaultResult? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: I think renaming it to paypalVaultResult
would be the most convenient option. This name was from before the refactor. What do you think?
@Published var paypalVaultToken: PayPalVaultResult? | |
@Published var paypalVaultResult: PayPalVaultResult? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember why I used Token as the name: to be consistent with updateSetupToken of UpdateSetupTokenResult type for card vaulting. In the same vault state, especially, I wanted the to have same naming convention
@@ -12,6 +12,7 @@ struct CardVaultState: Equatable { | |||
var setupToken: SetUpTokenResponse? | |||
var updateSetupToken: UpdateSetupTokenResult? | |||
var paymentToken: PaymentTokenResponse? | |||
var paypalVaultToken: String? | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it depends on the Merchant's needs. For now, we can leave it as is; It LGTM. In fact, if you'd like, we can even consider the approach used in the func cardDidCancel(_ cardClient: CardPayments.CardClient)
within the CardPaymentViewModel
, where we use the .idle state.
class PayPalVaultViewModel: VaultViewModel, PayPalVaultDelegate { | ||
|
||
@Published var paypalVaultToken: PayPalVaultResult? | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my perspective, communicating the viewModel with the view without using the State
structure somewhat breaks the paradigm. For now, I think it's better to move PayPalVaultViewModel.paypalVaultTokenResponse
and CardVaultViewModel.updateSetupTokenResponse
back to VaultState
, as it was in previous commits. Maybe someone else on the team could provide some input. Considering that the purpose of the Demo app is all about internal testing, showcase functionalities, and providing merchants with examples of SDK usage
@@ -14,7 +14,8 @@ struct Customer: Codable, Equatable { | |||
|
|||
struct PaymentSource: Decodable, Equatable { | |||
|
|||
let card: BasicCard | |||
var card: BasicCard? | |||
var paypal: BasicPayPal? | |||
} | |||
|
|||
struct BasicCard: Decodable, Equatable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does BasicCard and BasicPayPal mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
meaning they contain very minimal results like last four digits and email.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2cents: On Android we went with CardPaymentSource
and PayPalPaymentSource
. JSON encoding of this request is weird on Android too.
var paymentSourceDict: [String: Any] = [:] | ||
|
||
switch paymentSource { | ||
case .card: | ||
paymentSourceDict["card"] = [:] | ||
case .paypal(let usageType): | ||
paymentSourceDict["paypal"] = [ | ||
"usage_type": usageType, | ||
"experience_context": [ | ||
"return_url": "sdk.ios.paypal://vault/success", | ||
"cancel_url": "sdk.ios.paypal://vault/cancel" | ||
] | ||
] | ||
} | ||
|
||
let requestBody: [String: Any] = [ | ||
"customer": ["id": customerID], | ||
"payment_source": paymentSourceDict | ||
] | ||
|
||
return try? JSONSerialization.data(withJSONObject: requestBody) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using a raw dict + JSONSerizliation, throughout the rest of the demo app we conform structs to Encodable
& then use JSONEncoder
We should keep things consistent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey Sammy, I did this because I didn't know a way of using struct to make blank payment JSON object
{
"payment_source":
"card:" {}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know when we get the update setup token endpoint to work with custom url scheme, we will use experience context field but right now, it errors out with that field set with our custom url scheme so I am sending an empty card in the card vault request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember looking at this a while back and not finding a quick answer on Encodable
support for empty objects. Maybe we can use an empty struct
to represent [:]
like here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've never seen this. Thanks Steven. I will check it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We found EmptyParams()
|
||
struct PayPalVaultResultView: View { | ||
|
||
@ObservedObject var paypalVaultViewModel: PayPalVaultViewModel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ObservedObject var paypalVaultViewModel: PayPalVaultViewModel | |
@ObservedObject var viewModel: PayPalVaultViewModel |
🧶 : Similar to Rich's callout, since the var
is strongly typed here we can omit type from the name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to fix this one on last PR feedback fix commit
var paypalURL: String? { | ||
if let link = links.first(where: { $0.rel == "approve" }) { | ||
let url = link.href | ||
return url | ||
} | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var paypalURL: String? { | |
if let link = links.first(where: { $0.rel == "approve" }) { | |
let url = link.href | |
return url | |
} | |
return nil | |
} | |
var paypalURL: String? { | |
return links.first { $0.rel == "approve" }?.href | |
} |
🧶 : ^ would be technically equivalent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😅
var paypalURL: String? { | |
if let link = links.first(where: { $0.rel == "approve" }) { | |
let url = link.href | |
return url | |
} | |
return nil | |
} | |
var paypalURL: String? { | |
links.first { $0.rel == "approve" }?.href | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh nice haha. 🙌
Co-authored-by: Ricardo Herrera <[email protected]>
Co-authored-by: Ricardo Herrera <[email protected]>
Summary of changes
PaymentSource
in demo app to allowSetUpToken
creation for both card and PayPalVaultViewModel
inherited byCardVaultViewModel
andPayPalVaultViewModel
to reuse common properties and views.
CardVaultState
toVaultState
to define all common properties, move payment specific properties aspublished properties in their respective view models
SetUpToken
requestvault(url:)
function to launchASWebSession
ASWebSession
PayPalVaultDelegate
protocol, methods. AddPayPalVaultDelegate
protocol conformance toPayPalVaultViewModel
and implement methods inPayPalVaultViewModel
vault(url:)
in SDK throughPayPalWebCheckoutClient
's vault delegate functionsPaymentToken
call and Demo app outputvault(url:)
functionPayPalVaultNPVid.mp4
Checklist
Authors
@KunJeongPark