Skip to content
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

[V7] Add Encodable protocol for PayPal Checkout #1491

Open
wants to merge 22 commits into
base: v7
Choose a base branch
from

Conversation

richherrera
Copy link
Contributor

@richherrera richherrera commented Jan 2, 2025

Summary of changes

  • Add PayPalRequest protocol to define the contract for the base properties for BTPayPalCheckoutRequest and BTPayPalVaultRequest
  • Conform to the new protocol in the three concrete classes (BTPayPalRequest, BTPayPalCheckoutRequest and BTPayPalVaultRequest) to eliminate inheritance
  • Update docstrings for classes BTPayPalCheckoutRequest and BTPayPalVaultRequest
  • LineItems now conforms to the Encodable protocol.
  • Add encodable types for post bodies used in the PayPal Checkout flow.
  • PayPal Checkout has been tested in the Demo app to ensure the dictionaries are still constructed as expected.

Note: Since we have two different classes (PayPalCheckoutRequest and PayPalVaultRequest) used in PayPalClient, it's currently not possible to use Encodable until the same implementation is done for PayPalVaultRequest. We tested the flow, but we need to complete task DTMOBILES-1177 to be able to use Encodable.

Checklist

  • [ ] Added a changelog entry
  • Tested and confirmed payment flows affected by this change are functioning as expected

Authors

@richherrera richherrera requested a review from a team as a code owner January 2, 2025 18:21
if payPalRequest.requestBillingAgreement {
self.requestBillingAgreement = payPalRequest.requestBillingAgreement

if let billingAgreementDescription = payPalRequest.billingAgreementDescription {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, this property belongs to class BTPayPalRequest. After changing its properties to internal in a previous PR, merchants can no longer access these properties, meaning there's no way to configure, for example, "billingAgreementDescription" when creating an instance of BTPayPalCheckoutRequest or BTPayPalVaultRequest. Do you think we should add the necessary properties to the init of BTPayPalCheckoutRequest or BTPayPalVaultRequest, or make the properties in BTPayPalRequest public again and remove BTPayPalRequest.init?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great catch, I don't think we want to make the properties on BTPayPalRequest public since that would allow merchants to use the dot syntax, which we would like to move away from. I think it makes sense to add the properties to BTPayPalCheckoutRequest or BTPayPalVaultRequest inits then pass them in the super.init? What do you think?

Copy link
Contributor Author

@richherrera richherrera Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can opt to use a Protocol, this approach would be similar to the one we use on Android, where they use an AbstractClass. The reason for using a protocol is to define a contract for the necessary properties in both models. Instead of inheritance, we would use conformance so that our BTPayPalClient functions the same. I've updated the PR, let me know if you have any questions or comments. I'm happy to address any concerns.

@richherrera richherrera self-assigned this Jan 3, 2025
with configuration: BTConfiguration,
universalLink: URL? = nil,
isPayPalAppInstalled: Bool = false
) -> [String: Any] {
var baseParameters = super.parameters(with: configuration)
var experienceProfile: [String: Any] = [:]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we won't have inheritance, I had to move this code from BTPayPalRequest.parameters. In the next PR, this entire method will be removed because we are creating the PayPalCheckoutPOSTBody model, which conforms to the Encodable protocol.


static var callbackURLHostAndPath: String { get }

func parameters(
Copy link
Contributor Author

@richherrera richherrera Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is to mimic the inheritance we used to do

isShippingAddressEditable: Bool = false,
isShippingAddressRequired: Bool = false,
landingPageType: BTPayPalRequestLandingPageType = .none,
lineItems: [BTPayPalLineItem]? = nil,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing the implementation with Android, on Android, lineItems is only used for the Checkout flow. What do you think about removing this property from the PayPalRequest protocol, so it's only visible in the Checkout flow? On Android, we would need to remove the property from the AbstractClass PayPalRequest, and it wouldn't be overridden in PayPalCheckoutRequest

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, let's confirm with the pay with paypal team if line items can be passed on vault requests. If not, I agree with you we can remove it from the protocol and make it only available on PayPalCheckoutRequest

Comment on lines 63 to 64
let hermesPath: String
let paymentType: BTPayPalPaymentType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a preference on harding these here or below in the init? I lean slightly towards having them hardcoded at the top of the file, but am open to what we feel is best.

experienceProfile["no_shipping"] = !isShippingAddressRequired
experienceProfile["brand_name"] = displayName != nil ? displayName : configuration.json?["paypal"]["displayName"].asString()

if landingPageType?.stringValue != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take it or leave it since I think this will change in the next PR, but we tend to prefer the if let syntax over != nil

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated: e446f03

@@ -29,7 +29,7 @@ import BraintreeDataCollector
var clientMetadataID: String?

/// Exposed for testing the intent associated with this request
var payPalRequest: BTPayPalRequest?
var payPalRequest: PayPalRequest?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep the name as BTPayPalRequest for the protocol?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's an internal protocol, I think we can omit the BT prefix. If we decide to keep the prefix, how about I change it in the next PR? Right now, there would be naming conflicts with the concrete class. Another option would be to choose a different name for the protocol 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can change it in the next PR - I feel like it makes sense to keep the prefix for now just based on the class names but can be in the follow up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated: f90356b

Comment on lines 147 to 149
if let unitTaxAmount, !unitTaxAmount.isEmpty {
try container.encode(unitAmount, forKey: .unitTaxAmount)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to implement a custom encode() method just for this case?

Does Encodable not by default just omit an optional value that is nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is that on iOS, the value of unitAmount is used with the keys unit_amount

and unit_tax_amount

requestParameters["unit_tax_amount"] = unitAmount

but it seems this has always been an error. On Android it's different 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the PR: e478437 let me know what do you think. Unfortunately, the enums need to implement a custom encode because they are of type Int, and we are encoding their string value.

Copy link
Contributor

@jaxdesmarais jaxdesmarais left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants