A Flask-based API proxy that bridges Odoo and the WEX API for payment processing. It generates virtual cards for vendor payments, reliably forwards results to Odoo with TCP-like retries + ACKs, and includes comprehensive logging and error handling.
- Features
- Project Structure
- Prerequisites
- Configuration
- Development
- Deployment
- API Documentation
- Testing
- Troubleshooting
- 🔄 API Proxy: Seamless integration between Odoo and the WEX API
- 💳 Virtual Card Generation: Automated virtual card creation for payments
- 🔐 Authentication: Token in body (
x_studio_proxy_auth_token) or header (X-Proxy-Auth-Token) - 📊 Comprehensive Logging: Structured logs for debugging and monitoring
- 🧪 Test Mode: Built-in simulator; no external WEX calls
- 🐳 Docker Support: Containerized deployment with Compose
- 📣 Webhook Integration: Automatic forwarding of success/error to your downstream Odoo webhook
- 🛰️ Reliable Delivery (New): TCP-like retries with ACK from Odoo, exponential backoff, and idempotency fields (
_delivery_id,_delivery_attempt) - ⚡ Production Ready: Waitress WSGI server
odoo_wex_proxy/
├── app.py # Main Flask application
├── requirements.txt # Python dependencies
├── Dockerfile # Docker container configuration
├── docker-compose.yaml # Docker Compose setup
├── .env # Environment variables (not in repo)
└── README.md # This file
- Python 3.11+
- Docker and Docker Compose (optional but recommended)
- WEX API credentials
- Access to the downstream Odoo webhook endpoint
Create a .env file in the project root:
# Authentication
AUTH_TOKEN=your_secure_auth_token
# WEX API Configuration
WEX_API_URL=https://api.wex.com/merchant/log
WEX_USERNAME=your_wex_username
WEX_PASSWORD=your_wex_password
MERCHANT_CODE=*
# App Settings
TEST_MODE=false
WEBHOOK_URL=https://your-webhook-endpoint.com
# Reliable Delivery Controls (TCP-like)
MAX_DOWNSTREAM=5 # Max delivery attempts
ACK_TIMEOUT_SECONDS=10 # Wait for ACK after each attempt
DOWNSTREAM_RETRY_BASE_SECONDS=2 # Backoff base: 2,4,8,...
DOWNSTREAM_POST_TIMEOUT_SECONDS=10 # HTTP timeout per attempt| Variable | Description | Required | Default |
|---|---|---|---|
AUTH_TOKEN |
Token for securing /proxy & /ack (body x_studio_proxy_auth_token or header X-Proxy-Auth-Token) |
No | None |
WEX_API_URL |
WEX API endpoint URL | Yes | — |
WEX_USERNAME |
WEX API username | Yes | — |
WEX_PASSWORD |
WEX API password | Yes | — |
MERCHANT_CODE |
Merchant identifier for WEX | No | * |
TEST_MODE |
Simulate WEX responses | No | false |
WEBHOOK_URL |
Downstream Odoo webhook URL | Yes | — |
MAX_DOWNSTREAM |
Max downstream attempts (success only) | No | 5 |
ACK_TIMEOUT_SECONDS |
Seconds to wait for ACK per attempt | No | 10 |
DOWNSTREAM_RETRY_BASE_SECONDS |
Exponential backoff base seconds | No | 2 |
DOWNSTREAM_POST_TIMEOUT_SECONDS |
Timeout for each POST to webhook | No | 10 |
Idempotency: Each downstream delivery includes _delivery_id (stable UUID for the success) and _delivery_attempt (1..N). Your Odoo intake should de-duplicate by _delivery_id (or by payment_id if you prefer).
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
export FLASK_ENV=development
export FLASK_DEBUG=1
python app.pyApp listens on http://localhost:5000.
export TEST_MODE=true
python app.py-
Configure environment variables
cp .env.example .env # or create .env from the snippet above -
Deploy
docker-compose up -d
-
Check status & logs
docker-compose ps docker-compose logs -f odoo_wex_proxy
Service is available at http://localhost:8844.
docker build -t odoo_wex_proxy .
docker run -d \
--name wex_docker \
-p 8844:5000 \
--env-file .env \
odoo_wex_proxyProcesses a payment request from Odoo, calls WEX (or simulates), and starts reliable downstream delivery to WEBHOOK_URL. The HTTP response to the caller is immediate; retries happen in the background until an ACK is received.
Either send the token in the body or the header.
Body fields:
{
"x_studio_proxy_auth_token": "your_auth_token",
"_id": "unique_request_id",
"x_name": "payment_id",
"x_studio_vendor_name": "Vendor Company Name",
"x_studio_vendor_payment_amount_requested": 150.00,
"x_studio_hauler_invoice_or_remittance_advice_memo": "INV12345",
"x_studio_employee_name": "John Doe"
}Header alternative:
X-Proxy-Auth-Token: your_auth_token
This is the synchronous response to the caller. The downstream webhook will receive the same object plus
_delivery_idand_delivery_attempt.
{
"_model": "x_requests",
"_id": "unique_request_id",
"status": 200,
"card_number": "4111222233334444",
"expiration_month": "09",
"expiration_year": "2027",
"security_code": "123",
"payment_id": "payment_id",
"amount": "150.00",
"hauler_name": "Vendor Company Name",
"employee": "John Doe",
"invoice_number": "INV12345"
}Downstream delivery (example extra fields):
{
"...": "...",
"_delivery_id": "e8f0e6e8-1a9b-4d2f-8f7f-2a3b4c5d6e7f",
"_delivery_attempt": 2
}(Forwarded once to the downstream webhook; no retries.)
{
"_model": "x_requests",
"_id": "unique_request_id",
"status": 400,
"error": "Error message from WEX API"
}Odoo must call this to acknowledge successful processing of a success delivery. The proxy stops retrying once it receives the ACK.
{
"payment_id": "payment_id", // or "x_name"
"x_studio_proxy_auth_token": "your_auth_token"
}Header alternative:
X-Proxy-Auth-Token: your_auth_token
{
"status": "acknowledged",
"payment_id": "payment_id"
}Tip: If your Odoo intake is idempotent, you can safely ACK the first time you see a
_delivery_idyou haven’t processed yet.
-
Kick off a payment (test mode shown here):
curl -X POST http://localhost:8844/proxy \ -H "Content-Type: application/json" \ -H "X-Proxy-Auth-Token: your_token" \ -d '{ "_id": "test-123", "x_name": "PAY-001", "x_studio_vendor_name": "Test Vendor", "x_studio_vendor_payment_amount_requested": 100.50, "x_studio_hauler_invoice_or_remittance_advice_memo": "TEST-INV-001", "x_studio_employee_name": "Test Employee" }'
-
Simulate Odoo’s ACK once your webhook receives the success:
curl -X POST http://localhost:8844/ack \ -H "Content-Type: application/json" \ -H "X-Proxy-Auth-Token: your_token" \ -d '{ "payment_id": "PAY-001" }'
Observe logs for retry attempts and the “ACK received” message.
-
Confirm
WEBHOOK_URLis reachable from the container (network/firewall). -
Ensure your Odoo endpoint returns 200 OK (failures still count as “no ACK”).
-
Watch logs for lines like:
No ACK yet ... sleeping ...— tune:ACK_TIMEOUT_SECONDS(increase wait per attempt)MAX_DOWNSTREAM(more attempts)DOWNSTREAM_RETRY_BASE_SECONDS(slower/faster backoff)
- Deduplicate on
_delivery_id(recommended) orpayment_id. - ACK promptly after persisting; retries stop immediately on ACK.
- Use either body
x_studio_proxy_auth_tokenor headerX-Proxy-Auth-Token. - Verify
AUTH_TOKENin.env.
- Check credentials and
WEX_API_URL. - Use
TEST_MODE=trueto isolate proxy logic from WEX.
# Docker Compose
docker-compose logs -f odoo_wex_proxy
# Docker
docker logs -f wex_docker
# Local
# Logs print to console (DEBUG by default)- Use container status + logs (
docker ps,docker logs). - Optionally add your own healthcheck in Compose that curls
/proxywith-X OPTIONSor hits your Odoo webhook from a separate monitor.
MIT — see LICENSE.
For support and questions:
- Check Troubleshooting
- Review application logs
- Contact Computer Motivators: https://www.computermotivators.com
Built with ❤️ for our client in waste management.