donate.tpo->>user: responds with a fully-rendered donation form
user->>donate.tpo: submits the donation form with stripe/paypal details
donate.tpo->>donate.tpo: validates form, creates payment contract with stripe/paypal
donate.tpo->>civicrm: submits donation and perk info
donate.tpo->>user: redirects to donation thank you page
actor user
participant donate as donate tpo
participant civi as civicrm
participant pp as payment processor
Note left of civi: Perk data periodically exported from CiviCRM
Note right of donate: Perk data added to donate repo as JSON
user->>donate: Visits the donation site
donate->>user: Responds with a fully-rendered donation form
pp->>user: Embeds payment interface on page via vendor-hosted JS
user->>donate: Completes and submits donation form
donate->>donate: Validates form, creates payment contract with Stripe/PayPal
donate->>pp: Initiates payment process
donate->>user: Redirects to donation thank you page
pp->>donate: Sends webhook confirming results of transaction
donate->>civi: Submits donation and perk info
```
Another possible implementation was this:
...
...
@@ -539,8 +584,7 @@ graph TD
F --> H(civi gets the donation info from the django backend, and adds it to the civi database without trying to validate the donation amount or perks/swag)
```
As of this writing (August 2024), it's unclear whether the actual
design follows this, see [tpo/web/donate-neo#79](https://gitlab.torproject.org/tpo/web/donate-neo/-/issues/79) for the task of
See [tpo/web/donate-neo#79](https://gitlab.torproject.org/tpo/web/donate-neo/-/issues/79) for the task of
clarifying those docs.
### Review apps
...
...
@@ -620,8 +664,8 @@ the CiviCRM backend. It handles those types of transactions:
The Redis server runs on the CiviCRM server, and is accessed through
an IPsec tunnel, see the [authentication](#authentication) section below as
well. The Django application reimplements the [resque](https://resque.github.io/) queue
(originally written in Ruby, but ported to PHP by GiantRabbit) to pass
messages to the CiviCRM backend.
(originally written in Ruby, ported to PHP by GiantRabbit, and here ported to Python)
to pass messages to the CiviCRM backend.
Both types of donations and mailing list subscriptions are confirmed before
they are queued for processing by CiviCRM. In both cases, unconfirmed data
...
...
@@ -643,6 +687,10 @@ but they will not pair with any stored form data, so they are passed along to Ci
with a `recurring_billing_id` that CiviCRM uses to group them with a
recurring donation series.
Recurring PayPal donations first made on `donate-paleo` also issue legacy IPN messages,
and have a separate handler and validator from webhooks, but contain data conforming
to the Resque handler and so are passed to CiviCRM and processed in the same manner.
Confirming mailing list subscriptions works similarly to confirming donations,
but we also coordinate the confirmation process ourselves.
Donors who check the "subscribe me!" box in the donation form generate
...
...
@@ -658,9 +706,10 @@ outside of the purview of this writeup).
## Interfaces
Most of the interactions with donate happen over HTTP. Payment
providers ping back the site with webhook endpoints which have to
bypass CSRF protections.
Most of the interactions with donate happen over HTTP. Payment providers
ping back the site with webhook endpoints (and, in the case of legacy
`donate-paleo` NVP/SOAP API recurring payments, a PayPal-specific "IPN" endpoint)
which have to bypass CSRF protections.
The views handling these endpoints are designed to only reply with HTTP
status codes (200 or 400). If the message is legitimate but was malformed
...
...
@@ -748,7 +797,7 @@ documented in the [pyproject.toml](https://gitlab.torproject.org/tpo/web/donate-
poetry.lock](https://gitlab.torproject.org/tpo/web/donate-neo/-/blob/main/poetry.lock?ref_type=heads) file contains actual versions currently deployed.
Poetry is used to manage dependencies and builds. The frontend CSS /
JS code is managed with NVM. The [README file](https://gitlab.torproject.org/tpo/web/donate-neo/-/tree/main?ref_type=heads) has more information
JS code is managed with NPM. The [README file](https://gitlab.torproject.org/tpo/web/donate-neo/-/tree/main?ref_type=heads) has more information
about the development setup.
## Related services
...
...
@@ -818,6 +867,10 @@ Also note that the CiviCRM side of things has its own metrics, see the
## Tests
The `pytest` test suite can be run by entering a `poetry shell` and running:
coverage run manage.py test
To test donations after upgrades or to confirm everything works, see
the [Testing the donation site](#testing-the-donation-site) section.
...
...
@@ -960,6 +1013,18 @@ ensure no untrusted party is allowed to gain this privilege.
## Technical debt and next steps
### PII handling and Stripe Radar
`donate-neo` is severely opinionated about user PII; it attempts to handle
it as little as is necessary and discard it as soon as possible. This is
at odds with Stripe Radar's fraud detection algorithm, which weights
a given transaction as "less fraudulent" the more user PII is attached to it.
This clash is compounded by the number of well-intended donors using Tor
exit node IPs - some of which which bear low reputation scores with Stripe
due to bad behavior by prior users. This results in some transactions
being rejected due to receiving insufficient signals of legitimacy.
See Stripe's docs [here](https://docs.stripe.com/disputes/prevention/advanced-fraud-detection) and [here](https://docs.stripe.com/radar/integration).
### Dependencies chase
The `renovate-cron` project should be used on the donate-neo codebase