Easy Digital Downloads plugin
plugins/easy-digital-downloads/zcash-payment-edd is an Easy Digital Downloads (EDD) payment gateway that takes Zcash through your own copy of this service. It works with EDD’s block checkout and the classic [download_checkout] shortcode.
How it works
- The customer picks Zcash at checkout and places the order.
- The plugin records the order as Pending and calls
POST /payment-sessionwith the order total, the order number as the label, and EDD’s receipt page asreturn_url. It stores the issued address and payment ID on the order. - The customer is sent to the service’s pay page, which shows the amount, a QR code, and an Open in wallet link, and updates live.
- The service sends a signed webhook each time the address’s total changes. The plugin checks the
X-SignatureHMAC, ignores stale events, and marks the order Completed (which releases the downloads) once the total covers it. A partial payment adds an order note instead. - The pay page sends the customer back to the receipt page.
Edge cases the plugin handles:
- Waiting for confirmations. When the service sets
REQUIRED_CONFIRMATIONS, the plugin completes the order only once the lastreceived_by_confirmationselement covers it. A payment seen in the mempool, or with fewer confirmations, adds a pending confirmation order note and leaves the order Pending; a reorg that undoes those confirmations on a completed order puts it On hold. When the service doesn’t setREQUIRED_CONFIRMATIONS, the array is absent and the plugin usestotal_receivedas before. - An order keeps one Zcash address for life. If the order’s total changes after its address was issued, a payment to the old address is noted for you to reconcile rather than completing the order.
- Funds drop after payment (an unconfirmed transaction expires, or a reorg): the order goes On hold with a note. The plugin restores it only if it put the order on hold itself.
- Payment for a refunded, revoked, failed, or abandoned order is never completed automatically. Refunding or revoking an order with a Zcash address flags it for review, and later payments to it are left for you until you delete the order’s
_zcash_needs_reviewmeta. - After a partial payment, the pay page asks only for the remaining balance. The remainder is based on receipts the service has reported (see
WEBHOOK_REPORT_CONFIRMATIONS). - Overlapping webhook deliveries for one order are serialized with a database lock, and the event version is recorded only after the order update succeeds, so a failed update is retried.
If the service has no public pay page (no PUBLIC_BASE_URL and no Pay page base URL setting), the customer goes straight to the receipt page, which shows the address and a wallet link.
Setup
Requires WordPress 6.4+, PHP 8.1+, and Easy Digital Downloads 3.1+ (the plugin stays inactive on older EDD).
-
Run the service as in Deployment, with
ZCASH_WEBHOOK_URLset to your store’s webhook route and a randomZCASH_WEBHOOK_SECRET:ZCASH_WEBHOOK_URL=https://shop.example.com/?rest_route=/zcash-payment-edd/v1/webhookThe route differs from the WooCommerce plugin’s, so both plugins can run on one site. Choose
WEBHOOK_REPORT_CONFIRMATIONSbefore the service’s first start, as explained in the WooCommerce setup; the default0completes orders from unconfirmed transactions, which suits instant digital delivery but means a dropped transaction leaves files already delivered. -
Expose the pay page publicly as described in Exposing the page and set
PUBLIC_BASE_URL. SetRETURN_URL_ALLOWED_ORIGINSto your store’s origin. -
Copy
plugins/easy-digital-downloads/zcash-payment-eddintowp-content/plugins/(or zip the folder and upload it) and activate it. -
Under Downloads → Settings → Payments, enable Zcash (self-hosted), then on its Zcash section set:
- Service URL: where WordPress reaches the service, for example
http://zcash-payment-service:8787. This does not need to be public. - Webhook secret: the same value as
ZCASH_WEBHOOK_SECRET. - Pay page base URL: only if the service does not set
PUBLIC_BASE_URL. - ZEC per unit of store currency: only if the store does not price in ZEC.
- Service URL: where WordPress reaches the service, for example
The gateway hides itself at checkout until the service URL and secret are set.
Store currency
The plugin adds ZEC to EDD’s currency list and shows ZEC amounts with 8 decimal places. If the store prices in ZEC, order totals are sent as-is. If it prices in another currency, you must enter a fixed rate (ZEC per one unit of store currency) and keep it current yourself; the gateway hides itself when no rate is set. The plugin does not fetch prices from any third party.
Testing
Everything below runs without an EDD, WordPress.com, wallet, or exchange account, and without real or test coins.
Unit tests cover amount conversion, signature checks, webhook parsing, and pay URL handling:
php plugins/easy-digital-downloads/tests/unit.php
End-to-end test (plugins/easy-digital-downloads/e2e) starts MySQL, WordPress, and this service with Docker Compose, installs EDD from wordpress.org, configures the gateway, and drives a headless Chromium through the block checkout:
cd plugins/easy-digital-downloads/e2e
npm ci && npx playwright install --with-deps chromium # once per machine
./run.sh # KEEP_STACK=1 leaves it running on http://localhost:8080
The service runs on testnet with ZCASH_DEV_SIMULATED_PAYMENTS=true, which turns on POST /dev/simulate-payment. The test:
- buys a download and checks that the browser lands on the pay page with the right amount and a QR code;
- simulates a partial payment and checks the pay page shows it, then simulates the rest;
- checks that the browser returns to the receipt page and that the order is Completed with the full amount recorded;
- pays a revoked order and checks that the payment is held for review and the order stays revoked;
- simulates one payment at 0, 1, and 2 confirmations (the stack sets
REQUIRED_CONFIRMATIONS=2) and checks the order stays Pending with a pending confirmation note until the last; - checks that the webhook route rejects a bad signature with
401and acknowledges a signed event for an unknown address.
CI runs both on every pull request through .github/workflows/checks.yml, which calls .github/workflows/edd-plugin.yml, and keeps screenshots of each step as a build artifact.
What the tests do not cover
- The chain. Simulated payments skip lightwalletd and trial decryption, and their confirmations are simulated rather than mined.
- Wallet apps. Nobody scans the QR code or opens the
zcash:link in a real wallet. - Other setups. Only one EDD version, the default theme, and the block checkout are exercised.
- Non-ZEC pricing. The manual exchange rate path has unit tests but no end-to-end run.
- Email. The test stack has no mail server.