**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`/ <some other ID for recurring payments/ `txn_id` for one -time -payments>, name TBD ).
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](https://developer.paypal.com/docs/api/orders/v2/#orders_create) 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**
- add a field to the `PayPalPayment` model classCreate two new value objects called `TransactionId` and `PaypalRecurringPaymentId`. They should have one public readonly string property
- add the `externalId` property to the `PayPalPayment` model, with a union type of the aforementioned value objects and `null`, defaulting to `null`. Add a getter and setter. The setter should behave as follows
- setting a `TransactionId` is only allowed for unbooked, as an optional constructor parameter defaulting to an empty stringone-time payments
- `externalId` (is always a stringsetting `PaypalRecurringPaymentId` is only allowed for unbooked, recurring payments
- overriding is only allowed for when the value is `null` or has the same id (compare by value, has different structure/content depending on the type (recurring/one-time)not class instance)
- Add a custom `DoctrineType` class for mapping `externalId` to a string in the database (see `Iban` for comparison). Add athe mapping to the Doctrine database configuration
- 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:
- `completeNecessaryFieldsByContactingExternalProvider` receives a payment and returns a payment (it may return the same payment or a new payment instance with additional datapossibly modifying it).
- `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 a new `PayPalPayment` instance with the additional data.
- 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.
- The `completePaymentUrlByContactingExternalProvider` can return`completeNecessaryFieldsByContactingExternalProvider` should call `setExternalId` on the payment, creating a `PaypalRecurringPaymentId`. Do **not** store the Order ID, we don't need it at this point
- 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 `PaymentURLFactory` to return `IncompletePayPalURLGenerator` instead of `PayPalURLGenerator`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. You can remove the `PaypalAPIClient` dependency and make the `$payPalConfig` nullable (`PayPalURLGenerator` doesn't need a configuration)Use `LegacyPaypalUrlGeneratorConfig` as the type for `$payPalConfig`
- Change the `CreatePaymentUseCase`
- Use the `AdditionalFieldFetcherFactory` as a constructor dependency.
- call the `completeNecessaryFieldsByContactingExternalProvider` before 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