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

refactor: clarify architecture, implement PayZen & implement Wompi #53

Open
wants to merge 31 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
641ee80
feat: add new payment controller
blaggacao May 3, 2024
91cce32
docs: explain architecture
blaggacao May 3, 2024
993ab6d
feat: implement PayZen integration
blaggacao May 3, 2024
08f59da
fix: add back compatibility
blaggacao May 3, 2024
0d40a96
feat: add app logo to checkout page
blaggacao May 4, 2024
d503255
feat: add data capture to payment flow
blaggacao May 5, 2024
bbf52d5
fix: error reporting
blaggacao May 5, 2024
8b560ed
fix: polish checkout page
blaggacao May 5, 2024
69ecbbb
feat: add display of loyalty points and discount
blaggacao May 6, 2024
f23a17a
fix: oversight
blaggacao May 8, 2024
0389707
fix: payment doc event hooks
blaggacao May 8, 2024
761804f
fix: run server script after all other hooks
blaggacao May 8, 2024
a78444d
fix: ref doc processing
blaggacao May 8, 2024
93c4d9f
fix(payzen): notification processing
blaggacao May 8, 2024
638e16a
fix: wrong field setter
blaggacao May 19, 2024
fb7caea
fix: declined flow and states; allow to start over
blaggacao May 20, 2024
08fe551
fix: error cause
blaggacao Jun 1, 2024
037e566
feat: separate lp and discount
blaggacao Jun 2, 2024
c232c7e
feat: switch payment method after selecting
blaggacao Jun 8, 2024
4079aef
chore: cleanup & update
blaggacao Jun 18, 2024
054a9a0
fix: move preflight check to caller responsibility
blaggacao Jun 23, 2024
73b3110
feat: add display reference document
blaggacao Jun 23, 2024
88049c3
fix: ensure psl is not a hinderance to delete
blaggacao Jun 23, 2024
261b6ba
fix: handling of server exceptions
blaggacao Jul 3, 2024
19fb58f
fix: hide payments buttons after submit
blaggacao Jul 3, 2024
e5a9d14
feat: add redirect after milliseconds
blaggacao Jul 3, 2024
1c35421
feat: improve processing hook api
blaggacao Jul 7, 2024
d8239fe
feat: order buttons by priority
blaggacao Jul 10, 2024
e456828
feat: add debug functionality to payments/www/pay.py and payments/www…
blaggacao Sep 6, 2024
dc157d0
feat: Improve library detection using a negative list on window
blaggacao Sep 6, 2024
1802cd7
feat: Add exclude list for known global variables in pay.html
blaggacao Sep 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Architecture

The Payment app provides an abstract _PaymentController_ and specific implementations for a growing number of gateways.
These implementations are located inside the _Payment Gateways_ module.

Inside the _Payment_ module, an additional _Payment Gateway_ DocType serves as the link target to a reference DocType (RefDoc, see below) for the respective payment gateway controller and settings. For example, the _Payment Request_ DocType links to this _Payment Gateway_ in order to implement its payment flow.

Furthermore, upon installation, the app adds custom fields to the Web Form for facilitating web form based payments as well as a reference to the _Payment Session Log_ to the _Payment Request_ and removes them upon uninstallation.

## Relation between RefDoc and Payment Gateway Controller

The reference document implements the surrounding business logic, links to the _Payment Gateway_, and calls out — typically on submit — to the specific _PaymentController_ for initiating and handling the transaction.

After the transaction has been handled, the reference document may do post-processing in business logic via one of the available hook methods on the result and remit a specific payload (e.g., redirect link / success message) to the _PaymentController_.

During the entire lifecycle of a payment, state is maintained on the _Payment Session Log_ DocType. It allows persisting free-form data and serves as a central log for interaction with remote systems.

### TXData, TX Reference and Correlation ID

The data is passed from the RefDoc to the _PaymentController_ via a standardized data structure called _TXData_.

Then, all transaction lifecycle state is stored into a _Payment Session Log_.

The _Name_ of the _Payment Session Log_ will be the system's unique transaction reference to identify a payment transaction across its lifecycle and needs to be always passed around between the server, the client and remote systems. It may be typically stored in a gateway's request metadata in such a way that it is always returned by the remote server in order to reliably identify the transaction.

A payment gateway's _Correlation ID_, if available, is set as the _Payment Session Log_'s `correlation_id`. If a gateway uses it for fault-tolerant deduplication, a controller should send this ID to the remote server in any request throughout the remainder of a TX lifecycle.

If the remote server only returns a _Correlation ID_ but is unable to carry the _Payment Session Log_ name, then implementations can work around and recover the _Payment Session Log_ name, which is required by the controller methods, by filtering payment session logs on `{"correlation_id": correlation_id}`.

### RefDoc Flow

1. Call the _PaymentController_'s `initiate` `staticmethod` with with `tx_data` and eventually any pre-selected _Payment Gateway_, store the returning _Payment Session Log_ for further reference.
2. If called with a pre-selected _Payment Gateway_, use the returned `controller` from the previous step to call its `is_user_flow_initiation_delegated` method with the name of the _Payment Session Log_. If returning `True`, it means that the controller takes over the flow, if `False` the business logic on the RefDoc is in charge of the next steps.
4. If the user flow initiation is not delegated (to the controller): initiate/continue user flow, e.g., via Email, SMS, WhatsApp, Phone Call, etc.
5. post-process the payment status change via `on_payment_{mandate_acquisition,mandated_charge,charge}_processed` with a two-fold goal:
- Continue business logic in the backend
- Optional: return business-logic-specific `{"message": _("..."), "action": {"href": "...", "label": _("...")}}` to the controller, where:
- `message` is shown to the used
- `action` is rendered into the call to action after completing the payment
- If nothing is returned, the gateway's (if implemented) or app's standard is used

### PaymentController Flow

The _PaymentController_ flow has knowledge of the following flow variants:

- Charge: a single payment
- Mandate Acquisition: acquire a mandate for present or future mandated charges
- Mandated Charge: a single payment that requires little or no user interaction thanks to a mandate

A mandate represents some sort of pre-authorization for a future (mandated) charge. It can be used in diverse use cases such as:

- Subscription
- SEPA Mandate
- Preauthorized Charge ("hotel booking")
- Tokenized Charge ("one-click checkout")

Taking the flow variants into account, the high level control flow looks like this:

1. Eventually throw on `initiate` if `validate_tx_data` throws, if there's an issue.
2. Wait for the user GO signal (e.g., via link, call, SMS, click), then `proceed`. We delay remote interactions as much as possible in order to:
- Initialize timeouts as late as possible
- Offer the customer choices until the last possible moment (among others: mandate vs charge)
- The controller can change the _TX Data_ with the user input
3. Based on this most recent _TX Data_: decide whether to initiate a Charge, a Mandate Acquisition, or a Mandated Charge. Mandate Acquisition is typically implemented as a pre-processing step of a Mandated Charge.
4. Initiate the flow via the dedicated method `_initiate_*` method and the updated _TX Data_.
6. Manage the actual payment capture along the user flow in collaboration with the payment gateway.
7. Process the result via the dedicated method `_process_response_for_{charge,mandated_charge,mandate_acquisition}`:
- Validate the response payload via `_validate_response_payload`, for example, check the integrity of the message against a pre-shared key.
8. Invoke the RefDoc's `on_payment_{mandate_acquisition,mandated_charge,charge}_processed()` method and manage the finalization user flow (e.g., via message and redirect).

### Schematic Overview

![Schematic Overview](./overview.excalidraw.svg "Schematic Overview")

### Sequence Diagram

```mermaid
sequenceDiagram
participant RefDoc
participant PaymentController
actor Payer
actor Gateway
autonumber

rect rgb(200, 150, 255)
RefDoc->>+PaymentController: initiate(txdata, payment_gateway)
Note over PaymentController: Status - "Created"
Note over RefDoc, Gateway: Payer is instructed to proceed
Payer ->> PaymentController: proceed(pslname, updated_txdata)
Note over PaymentController: Status - "Initiated"
PaymentController->>+Gateway: _initiate_*()
alt IPN
Gateway->>-PaymentController: process_reponse(pslname, payload)
else ClientFlow
Gateway-->>Payer:
Payer->>PaymentController: process_reponse(pslname, payload)
end
end
rect rgb(70, 200, 255)
PaymentController -->> PaymentController: _validate_response()
PaymentController -->> PaymentController: _process_response_for_*()
opt RefDoc implements hook
PaymentController ->> RefDoc: on_payment_*_processed(flags, state)
RefDoc-->>PaymentController: return_value
end
PaymentController -->> PaymentController: persist status

Note over PaymentController: Status - "Authorized|Processing|Paid|Failed|Error|Error - RefDoc"
PaymentController -->- Payer: return data to caller for rendering
end
```
> **Notes:**
>
> - A server-to-server response from the gateway and a signed payload via the client flow may occur in parallel.
> - Thus, the blue area is expected to be **idempotent**
> - A processing lock only ensures that no parallel processing would lead to a race

### And my Payment URL?

The payment URL is a well-known URL with a `s` query parameter and the page at that URL can be used for capturing the user's GO signal to the Payment controller flow.

It is kept short, tidy, and gateway-agnostic in order to impress succinct trustworthiness on the user.

Format: `https://my.site.tld/pay?s=<Payment Session Log Name>`.

## Other Folders

All general utils are stored in the [utils](payments/utils) directory. The utils are written in [utils.py](payments/utils/utils.py) and then imported into the [`__init__.py`](payments/utils/__init__.py) file for easier importing/namespacing.

The [overrides](payments/overrides) directory has all the overrides for overriding standard Frappe code. Currently, it overrides the WebForm DocType controller as well as a WebForm whitelisted method.

The [templates](payments/templates) directory has all the payment gateways' custom checkout pages.

The file [`payments/types.py`](`payments/types.py) define relevant types and dataclasses for ease of developing new integrations with an IDE.
The relevant exceptions of this app can be found in [`payments/exceptions.py`](payments/exceptions.py).

The _Payment Controller_ base class is implemented in [`payments/controllers/payment_controller.py`](payments/controllers/payment_controller.py) and a unified checkout page is implemented in [`payments/www/pay.js`](payments/www/pay.js), [`payments/www/pay.css`](payments/www/pay.css), [`payments/www/pay.html`](payments/www/pay.html) and [`payments/www/pay.py`](payments/www/pay.py), respectively.

22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ A payments app for frappe.
```
$ bench --site <sitename> install-app payments
```

## Supported Payment Gateways

## App Structure & Details
App has 2 modules - Payments and Payment Gateways.
- Razorpay
- Stripe
- Braintree
- Paypal
- PayTM
- Mpesa
- GoCardless

Payment Module contains the Payment Gateway DocType which creates links for the payment gateways and Payment Gateways Module contain all the Payment Gateway (Razorpay, Stripe, Braintree, Paypal, PayTM) DocTypes.
## Architecture

App adds custom fields to Web Form for facilitating payments upon installation and removes them upon uninstallation.
see [Architecture Document](./ARCHITECTURE.md)

All general utils are stored in [utils](payments/utils) directory. The utils are written in [utils.py](payments/utils/utils.py) and then imported into the [`__init__.py`](payments/utils/__init__.py) file for easier importing/namespacing.

[overrides](payments/overrides) directory has all the overrides for overriding standard frappe code. Currently it overrides WebForm DocType controller as well as a WebForm whitelisted method.

[templates](payments/templates) directory has all the payment gateways' custom checkout pages.

#

## License
MIT ([license.txt](license.txt))
21 changes: 21 additions & 0 deletions overview.excalidraw.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions payments/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .payment_controller import PaymentController, frontend_defaults
Loading
Loading