-
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 with Purchase PayPalWeb #221
Changes from 2 commits
11da801
e7abc8e
dc0c32e
f07d0bc
62480f6
52a662f
c77464c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ struct CreateOrderParams: Encodable { | |
let applicationContext: ApplicationContext? | ||
let intent: String | ||
var purchaseUnits: [PurchaseUnit]? | ||
var paymentSource: VaultCardPaymentSource? | ||
var paymentSource: VaultPaymentSource? | ||
} | ||
|
||
struct ApplicationContext: Codable { | ||
|
@@ -17,6 +17,38 @@ struct ApplicationContext: Codable { | |
} | ||
} | ||
|
||
enum VaultPaymentSource: Encodable { | ||
case card(VaultCardPaymentSource) | ||
case paypal(VaultPayPalPaymentSource) | ||
|
||
func encode(to encoder: Encoder) throws { | ||
var container = encoder.singleValueContainer() | ||
switch self { | ||
case .card(let cardSource): | ||
try container.encode(cardSource) | ||
case .paypal(let paypalSource): | ||
try container.encode(paypalSource) | ||
} | ||
} | ||
} | ||
|
||
struct VaultPayPalPaymentSource: Encodable { | ||
|
||
let paypal: VaultPayPal | ||
} | ||
|
||
struct VaultPayPal: Encodable { | ||
|
||
let attributes: Attributes | ||
let experienceContext: ExperienceContext | ||
} | ||
|
||
struct ExperienceContext: Encodable { | ||
|
||
let returnURL: String | ||
let cancelURL: String | ||
} | ||
|
||
struct VaultCardPaymentSource: Encodable { | ||
|
||
let card: VaultCard | ||
|
@@ -30,15 +62,17 @@ struct VaultCard: Encodable { | |
struct Attributes: Encodable { | ||
|
||
let vault: Vault | ||
let customer: CardVaultCustomer? | ||
let customer: VaultCustomer? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we can just call this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed here: 62480f6 |
||
} | ||
|
||
struct Vault: Encodable { | ||
|
||
let storeInVault: String | ||
let usageType: String? | ||
let customerType: String? | ||
} | ||
|
||
struct CardVaultCustomer: Encodable { | ||
struct VaultCustomer: Encodable { | ||
|
||
let id: String | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,8 @@ struct CreateOrderPayPalWebView: View { | |
@ObservedObject var paypalWebViewModel: PayPalWebViewModel | ||
|
||
@State private var selectedIntent: Intent = .authorize | ||
@State private var vaultCustomerID: String = "" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought you had said during stand that regardless of what's customer ID is passed in the API always generates one? If that's the case, I think we should remove this from our demo. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, my plan is to do that today. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
@State var shouldVaultSelected = false | ||
|
||
let selectedMerchantIntegration: MerchantIntegration | ||
|
||
|
@@ -22,6 +24,11 @@ struct CreateOrderPayPalWebView: View { | |
Text("CAPTURE").tag(Intent.capture) | ||
} | ||
.pickerStyle(SegmentedPickerStyle()) | ||
HStack { | ||
Toggle("Should Vault with Purchase", isOn: $shouldVaultSelected) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks great! |
||
Spacer() | ||
} | ||
FloatingLabelTextField(placeholder: "Vault Customer ID (Optional)", text: $vaultCustomerID) | ||
ZStack { | ||
Button("Create an Order") { | ||
Task { | ||
|
@@ -30,7 +37,10 @@ struct CreateOrderPayPalWebView: View { | |
try await paypalWebViewModel.createOrder( | ||
amount: "10.00", | ||
selectedMerchantIntegration: DemoSettings.merchantIntegration, | ||
intent: selectedIntent.rawValue) | ||
intent: selectedIntent.rawValue, | ||
shouldVault: shouldVaultSelected, | ||
customerID: vaultCustomerID.isEmpty ? nil : vaultCustomerID | ||
) | ||
} catch { | ||
print("Error in getting setup token. \(error.localizedDescription)") | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,6 +39,14 @@ struct PayPalWebOrderCompletionResultView: View { | |
LeadingText("Email", weight: .bold) | ||
LeadingText("\(email)") | ||
} | ||
if let vaultID = orderResponse.paymentSource?.paypal?.attributes?.vault.id { | ||
LeadingText("Vault ID / Payment Token", weight: .bold) | ||
LeadingText("\(vaultID)") | ||
} | ||
Comment on lines
+42
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. General question: is there an agreed upon name that JS calls this in our public docs? I see we use both vault ID and payment token here. We should use whichever name is used in our public docs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think for the customer point of view, we call it payment token , but we reference it as vault ID in the APIs. |
||
if let customerID = orderResponse.paymentSource?.paypal?.attributes?.vault.customer.id { | ||
LeadingText("Customer ID", weight: .bold) | ||
LeadingText("\(customerID)") | ||
} | ||
Text("") | ||
.id("bottomView") | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,15 +21,20 @@ class CardPaymentViewModel: ObservableObject, CardDelegate { | |
let amountRequest = Amount(currencyCode: "USD", value: amount) | ||
// TODO: might need to pass in payee as payee object or as auth header | ||
|
||
var vaultPaymentSource: VaultCardPaymentSource? | ||
var vaultCardPaymentSource: VaultCardPaymentSource? | ||
if shouldVault { | ||
var customer: CardVaultCustomer? | ||
var customer: VaultCustomer? | ||
if let customerID { | ||
customer = CardVaultCustomer(id: customerID) | ||
customer = VaultCustomer(id: customerID) | ||
} | ||
let attributes = Attributes(vault: Vault(storeInVault: "ON_SUCCESS"), customer: customer) | ||
let attributes = Attributes(vault: Vault(storeInVault: "ON_SUCCESS", usageType: nil, customerType: nil), customer: customer) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we default There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. addressed here: c77464c |
||
let card = VaultCard(attributes: attributes) | ||
vaultPaymentSource = VaultCardPaymentSource(card: card) | ||
vaultCardPaymentSource = VaultCardPaymentSource(card: card) | ||
} | ||
|
||
var vaultPaymentSource: VaultPaymentSource? | ||
if let vaultCardPaymentSource { | ||
vaultPaymentSource = .card(vaultCardPaymentSource) | ||
} | ||
|
||
let orderRequestParams = CreateOrderParams( | ||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -10,15 +10,46 @@ class PayPalWebViewModel: ObservableObject, PayPalWebCheckoutDelegate { | |||
|
||||
let configManager = CoreConfigManager(domain: "PayPalWeb Payments") | ||||
|
||||
func createOrder(amount: String, selectedMerchantIntegration: MerchantIntegration, intent: String) async throws { | ||||
func createOrder( | ||||
amount: String, | ||||
selectedMerchantIntegration: MerchantIntegration, | ||||
intent: String, | ||||
shouldVault: Bool, | ||||
customerID: String? = nil | ||||
) async throws { | ||||
// might need to pass in payee as payee object or as auth header | ||||
|
||||
let amountRequest = Amount(currencyCode: "USD", value: amount) | ||||
// TODO: might need to pass in payee as payee object or as auth header | ||||
|
||||
var vaultPayPalPaymentSource: VaultPayPalPaymentSource? | ||||
if shouldVault { | ||||
var customer: VaultCustomer? | ||||
if let customerID { | ||||
customer = VaultCustomer(id: customerID) | ||||
} | ||||
let attributes = Attributes( | ||||
vault: Vault( | ||||
storeInVault: "ON_SUCCESS", | ||||
usageType: "MERCHANT", | ||||
customerType: "CONSUMER" | ||||
), | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just hard coded this for direct merchant but I am investigating what different parameters are for different selected merchant integrations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should get input from product on what we need to support here for v1 in the demo app. -Victoria : yes, Walter referenced docs during stand and I plan on working with Nirvan and Walter on the specs. So right now, in demo app we only have direct, connected and managed path configured, not specific paths like CP2 or CP4. The usageType would depend on specific integration patterns. I think specs for server side inputs like auth headers and payee info should be made for each integration types in another PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, Walter referenced docs during stand and I plan on working with Nirvan and Walter on the specs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So right now, in demo app we only have direct, connected and managed path configured, not specific paths like CP2 or CP4. The usageType would depend on specific integration patterns. I think specs for server side inputs like auth headers and payee info should be made for each integration types in another PR. |
||||
customer: customer | ||||
) | ||||
let paypal = VaultPayPal(attributes: attributes, experienceContext: ExperienceContext(returnURL: "https://example.com/returnUrl", cancelURL: "https://example.com/cancelUrl")) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In your video, the redirect back to the Demo app looked fine, even though you would think the experienceContext should be a URL scheme to the app. What happens if the merchant doesn't set these URLs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The endpoint throws an error without the experienceContext field for PayPal vaulting. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In our SDK, when we launch the ASWebSession for PayPalWeb, we construct the redirectURLString and append it as a query parameter to the checkout url :
The redirect url is included as a parameter in the url that we construct to launch the webSession. I don't know if this is a typo or intentional but query parameter name for redirect url is strange. So this experienceContext in create Order API is only relevant to JS SDK but the API fails nonetheless There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. :sigh: That's right, this is an issue with the create-order API. It doesn't accept non-https URLs. We should add a comment to make this issue clear in the code. That basically createOrder requires an https URL, which doesn't get used by the SDK flow at all. The merchant could put There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. That might be an actual site. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comments above to clarify that required experienceContext fields for PayPal vault with purchase are not used in the SDK. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't suppose there is a way for use to pass a URL on our merchants behalf so they don't need to do this step? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The createOrder will just error if that URL value isn't set or is an empty string. It also errors if the string doesn't start with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Roger There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Created - DTPPCPSDK-1492 |
||||
vaultPayPalPaymentSource = VaultPayPalPaymentSource(paypal: paypal) | ||||
} | ||||
|
||||
var vaultPaymentSource: VaultPaymentSource? | ||||
if let vaultPayPalPaymentSource { | ||||
vaultPaymentSource = .paypal(vaultPayPalPaymentSource) | ||||
} | ||||
|
||||
let orderRequestParams = CreateOrderParams( | ||||
applicationContext: nil, | ||||
intent: intent, | ||||
purchaseUnits: [PurchaseUnit(amount: amountRequest)] | ||||
purchaseUnits: [PurchaseUnit(amount: amountRequest)], | ||||
paymentSource: vaultPaymentSource | ||||
) | ||||
|
||||
do { | ||||
|
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.
Just to clarify my understanding - a merchant only needs to set the
paymentSource
in their create order call if they want to vault, correct?How do we document this to a merchant?
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, JS SDK has documentation on this on their developer site. We should do something similar.