Page MenuHomePhabricator

Adapt CreatePayment use case to allow storing external payment ID from payment provider
Closed, ResolvedPublic8 Estimated Story Points

Description

Problem
When using the (new) PayPal API the PayPalNotificationController, PayPal no longer sends the donation ID and token in the IPN, but only an ID (recurring_payment_id for recurring payments/ txn_id for one-time-payments ).

Being able to look up donations/memberships is necessary for the proper function of the notification controller to

a) make sure we book the right item
b) determine which type of item (donation/membership) we book

The process for one-time-payments using the Order API also involves storing the order ID first, "capturing" the order with a second API request using the order ID and then storing the transaction ID.

Solution
We need to change the way we create payments in CreatePaymentUseCase:
Before storing the payment, we need to call the PayPal API (or rather an abstracted interface for the payment provider) and receive an ID that we store with the payment. This ID can then be used for the donation/membership lookup.

The code that requests the ID data from PayPal already exists in our PayPalAPI adapter class and is used by PayPalAPIUrlGenerator (where we ignore the ID).

The result from the PayPal API contains the payment URL and the ID. Only the ID is relevant for the payment domain, the URL is ephemeral and should not be stored.

We won't be using the PayPal API URL generator for one-time payments, but should prepare as much we can for future use.

Necessary refactoring steps in the payment bounded context

  • Create two new entities called PaypalOrder and PaypalSubscription. They should have one public readonly string property called externalId and a PaypalPayment property. You can get the unique id (primary key) value from the payment, as it's a 1:1 relationship. The classes may be based on the same abstract superclass.
  • Add Doctrine XML class for mapping the new classes using single table inheritance
  • Create two new implementations of PaymentProviderURLGenerator for PayPal, an "initial" one that throws a LogicException (IncompletePayPalURLGenerator) and one that receives a URL PayPalURLGenerator.
  • introduce AdditionalFieldFetcher interface for contacting payment provider. it should have two methods:
    • fetchAndStoreAdditionalData receives a payment and returns a payment (possibly with side effects storing).
    • completePaymentUrlByContactingExternalProvider receives a PaymentProviderURLGenerator and returns it (it may return the same instance or return a new one)
  • Create implementations of the AdditionalFieldFetcher interface:
    • A NoChange implementation that returns the payment and url generator unchanged (will be used for all payments except PayPal)
    • A PayPal implementation that contacts PayPal API (depending on recurring/non-recurring), using data from the payment and config object and create and store PayPalPayment PaypalOrder and PaypalSubscription entities
      • Move the code for this from PayPalAPIUrlGenerator into the implementation.
      • The instance should have a configuration value object similar to PayPalAPIURLGeneratorConfig (you can duplicate it)
      • You can get URL and ID from the Order or Subscription objects returned by the API.
      • fetchAndStoreAdditionalData should store a PaypalSubscription . Do not create/store a PaypalOrder, we don't need it at this point (the payment flow/logic for orders is different)
      • The completePaymentUrlByContactingExternalProvider should replace the IncompletePayPalURLGenerator with a PayPalURLGenerator
      • You *must not* call the API twice (once for each implemented method). Instead, store the URL and ID after calling the API once and return them
  • Create an AdditionalFieldFetcherFactory interface that receives a payment and returns an AdditionalFieldFetcher instance based on the type. To make testing easier, you should make PayPalAPI a constructor dependency of the factory.
  • Change the PaymentURLFactory:
    • Add a proper feature flag (e.g. usePayPalAPI) that toggles the usage of the LegacyPaypalUrlGenerator (instead of checking the configuration type). You may use a setter that toggles it instead of a constructor property
    • The factory for the PayPal URL generator must have the following logic:
      • If the feature flag is false, use the LegacyPaypalUrlGenerator
      • If the payment is a one-time payment, use the LegacyPaypalUrlGenerator (we'll change this in the future when we can support the Order flow of the Paypal API)
      • If the payment is a one-time payment return IncompletePayPalURLGenerator (not PayPalURLGenerator).
    • You can remove the PaypalAPIClient dependency. Use LegacyPaypalUrlGeneratorConfig as the type for $payPalConfig
  • Change the CreatePaymentUseCase
    • Use the AdditionalFieldFetcherFactory as a constructor dependency.
    • call the fetchAndStoreAdditionalData after storing the payment
    • Instantiate the URL generator and call the completePaymentUrlByContactingExternalProvider, returning the new URL generator in the result
    • fix the failing tests. Pass a Fake implementation of PayPalAPI for PayPal to AdditionalFieldFetcherFactory
  • Delete PayPalAPIUrlGenerator and PayPalAPIURLGeneratorConfig class and their tests

Event Timeline

gabriel-wmde set the point value for this task to 8.