A payment collection system for oTree experiments that encrypts participant payment data using OpenPGP encryption. Encrypts payment data to lab manager. Can be adapted for use at other labs. Intended to provide a “best effort” mitigation when using out-of-state service providers.
This project is licensed under the GNU Lesser General Public License version 3.0 or any later version, consistent with the included OpenPGP.js library. This software is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.
© The University of Melbourne, 2025.
The E²MU Payment Apps consist of two oTree apps that handle secure collection of participant payment information:
- e2mupay_start: Initializes the payment system and records session start time
- e2mupay_end: Collects encrypted payment data from participants
Payment data is encrypted client-side using OpenPGP.js before being sent to the server, ensuring participant privacy and data security. Only the lab manager can decrypt the payment data. This makes collecting payment data secure even when using other people's servers.
otree/
├── e2mupay_start/ # Start app - initializes payment system
│ ├── __init__.py # App logic and timezone configuration
│ ├── Hello.html # Simple page template
│ └── e2mupay_static.tgz # Contains OpenPGP.js and encryption key
├── e2mupay_end/ # End app - collects payment data
│ ├── __init__.py # App logic and timezone configuration
│ ├── PaymentData.html # Payment data collection form
│ └── Results.html # Confirmation page
├── dummy/ # Example app showing usage
│ └── __init__.py # Sets e2mupay_amount variable
└── settings.py # Example oTree configuration
- Client-side encryption: All payment data is encrypted using OpenPGP before transmission
- Static files auto-extract: Experimenters only need e2mupay_start and e2mupay_end
- Multiple payment methods: Supports PayID and Australian bank accounts
- Form validation: Client-side validation for all payment fields
- Payment codes: Generates unique 9-character alphanumeric codes for each participant
- Timezone handling: Configurable timezone for timestamp recording
- Bootstrap UI: Modern, responsive interface
-
Put e2mupay_start and e2mupay_end into your project (as is!). Click here to download this whole repository. You can find the necessary apps in the
otreefolder. -
Set payment amount in your experiment:
# Somewhere in your experiment (probably at the end) player.participant.vars["e2mupay_amount"] = 17.42 # Set final payment in real currency
-
Configure app sequence:
# In settings.py SESSION_CONFIGS = [ dict( name="your_experiment", app_sequence=[ "e2mupay_start", # Must be first "your_app1", "your_app2", # ... your other apps "e2mupay_end" # Must be last ], ..., ), ]
Note: otree/dummy contains an example app. In fact, otree is an “oTree project.”
External laboratories need to perform additional configuration steps before distributing this software to experimenters:
- Extract the current static files:
tar -xzf otree/e2mupay_start/e2mupay_static.tgz - Replace
e2mupay/key.jswith your own public key in the following format:const pubkey = `-----BEGIN PGP PUBLIC KEY BLOCK----- YOUR_PUBLIC_KEY_HERE -----END PGP PUBLIC KEY BLOCK-----`;
- Repackage:
tar -czf otree/e2mupay_start/e2mupay_static.tgz e2mupay/
Update the timezone in both start and end apps:
In otree/e2mupay_start/__init__.py:
dt_local = datetime.now(zoneinfo.ZoneInfo("Your/Timezone"))In otree/e2mupay_end/__init__.py:
dt_local = datetime.now(zoneinfo.ZoneInfo("Your/Timezone"))Common timezone examples:
"Australia/Melbourne""America/New_York""Europe/London""Asia/Tokyo"
Instead of E²MU, insert your lab's name. E²MU will not assist you in any way if you mistakenly send subjects to us, or encrypt payment data to E²MU's lab manager. That's your problem.
Also, you might need to change some of the payment methods or the prelude to the form.
- Python 3.8+
- oTree 5 or higher
The payment form collects:
-
Personal Information:
- First name (must match bank account)
- Last name (must match bank account)
- Email address (emergency contact only)
-
Payment Method (choose one):
- PayID: Email address or Australian mobile number (04xxxxxxxx)
- Bank Account: Account number (6-10 digits) and BSB (6 digits)
This step is only ever performed by the lab manager.
The data from the column e2mupay_end.1.player.pay_to can be decrypted on GNU/Linux using sed 's/|/\n/g' | gpg. Note how | is replaced by newline.
The script decrypt.py contains a function that does this for you (useful for batch payments, requires python-gnupg).
- All payment data is encrypted client-side using OpenPGP.js
- Only authorized officials can decrypt the data
- Payment data is not linked to experimental behavior data
- Data remains within the state of Victoria (for E²MU!)
Each participant receives a unique 9-character payment code (format: XXX-XXX-XXX) that should be:
- Saved by the participant until payment is received
- Quoted when contacting the lab about payment issues
- Used for payment tracking and verification
- e2mupay_start: Records session start timestamp in
Australia/Melbournetimezone - Your experiment: Sets final payment amount using
player.participant.vars["e2mupay_amount"] - e2mupay_end:
- Displays payment form with amount
- Validates all fields client-side
- Encrypts data using OpenPGP public key
- Stores encrypted data and payment metadata
- Shows confirmation with payment code
The Player model in e2mupay_end stores:
code: 9-character payment codeamount: Payment amount from participant varsstart_: ISO timestamp from e2mupay_startend_: ISO timestamp from e2mupay_endpay_to: Encrypted payment data string
Encrypted data contains:
LASTNAME, Firstname
email@example.com
PayID: payid@example.com
OR
Account: 123456789
BSB: 123-456
Additional metadata (NOT AUTHORITATIVE): session_code, participant_code, payment_code, amount
- The
e2mupay_start/Hellopage is not displayed (is_displayedreturnsFalse) - Payment form has a 60-minute timeout
- BSB numbers are automatically formatted with dashes
- Form includes comprehensive client-side validation
- Modal confirms data before encryption/submission