Donor Merchant

Description

Donor Merchant is a lightweight donation plugin for nonprofits, churches, schools, and community groups. Drop the donation form on any page with a block or shortcode, connect Stripe or PayPal, and start accepting gifts in minutes.

Unlike most donation plugins, recurring giving is included in the free plugin – monthly, quarterly, and yearly gifts powered by Stripe subscriptions, at no extra cost and with no transaction fees added by us.

Features

  • One-time and recurring donations – monthly, quarterly, and yearly giving built in, free.
  • Campaigns with goal thermometers – run multiple donation forms, each with its own goal, suggested amounts, and description, and show a live progress bar.
  • Donor portal – a passwordless, magic-link dashboard where donors view their giving history, download receipts, and manage recurring gifts (update card or cancel) via Stripe’s secure billing portal.
  • Printable receipts & annual tax statements – every gift gets a save-as-PDF receipt, and donors can download a year-end giving statement. No PDF library bloat.
  • Import your history – move donor and donation records in from GiveWP, Charitable, Donorbox, or a spreadsheet by CSV, with automatic column detection, a preview step, and duplicate protection.
  • Offline donations – record checks, cash, and gifts taken elsewhere so donor totals stay complete.
  • Stripe – cards, Apple Pay, Google Pay, and bank debit (ACH) via Stripe’s secure Payment Element. Card details never touch your server.
  • PayPal – one-time gifts through Smart Buttons, plus recurring PayPal subscriptions.
  • Designations / funds – let donors choose which fund their gift supports.
  • Integrations – outgoing webhooks (Zapier, Make, or anything) and Mailchimp audience sync.
  • Custom fields – add your own questions to the donation form.
  • Fee recovery – let donors add a little extra so 100% of the gift reaches you.
  • Tributes and memorials – donors can dedicate a gift in honor or in memory of someone.
  • Donor management – every donor and gift tracked automatically, with lifetime totals and CSV export.
  • Email receipts – customizable thank-you receipts and new-donation notifications.
  • Reports dashboard – total raised, monthly totals, average gift, recurring count at a glance.
  • Test mode – try everything end to end with Stripe test keys and the PayPal sandbox before going live.
  • International – 20 currencies supported.
  • Block and shortcodes – the Donation Form block, plus [donor_merchant], [donor_merchant campaign="N"], [donor_merchant_progress campaign="N"], and [donor_merchant_portal].

How it stays secure

Payment details are collected by Stripe and PayPal directly in the donor’s browser. No card number, expiration date, or CVC is ever transmitted to or stored on your server. Donation totals are always recomputed server-side, payment status is confirmed server-to-gateway, and webhook signatures are verified.

External services

To process donations this plugin connects to third-party payment and (optionally) marketing services. Nothing is sent until you enable and configure a given service.

  • Stripe (only when Stripe is enabled). The Stripe.js library is loaded from https://js.stripe.com on pages that display the donation form, so payment details can be collected securely in the donor’s browser. Your server also calls the Stripe API at https://api.stripe.com to create payment intents, customers, and subscriptions, and to verify payments. Data sent: donation amount and currency, the donor’s name, email and phone number if given, an internal reference for the donation, and the payment details the donor enters (handled by Stripe – they do not touch your server). Stripe terms: https://stripe.com/legal – privacy: https://stripe.com/privacy
  • PayPal (only when PayPal is enabled). The PayPal JS SDK is loaded from https://www.paypal.com on pages with the donation form. Your server calls the PayPal API (https://api-m.paypal.com, or https://api-m.sandbox.paypal.com in sandbox mode) to create and capture orders and subscriptions. Data sent: donation amount, currency, and an internal reference. PayPal terms: https://www.paypal.com/legalhub – privacy: https://www.paypal.com/privacy
  • Mailchimp (optional – only when you enable it). By default the form shows a “keep me updated” checkbox and only opted-in donors are sent. If you switch that checkbox off in the settings, every donor is added to the audience, so leave it on unless you have another basis for contacting them. Your server calls the Mailchimp API (https://<dc>.api.mailchimp.com) to add the donor to the audience you choose. Data sent: donor email and name. Mailchimp terms: https://mailchimp.com/legal/terms/ – privacy: https://mailchimp.com/legal/privacy/
  • Outgoing webhooks (optional). If you enter one or more webhook URLs, completed-donation data is POSTed to those URLs – which you control and choose (e.g. your Zapier or Make endpoint). No third party receives data unless you configure it.

Getting started

  1. Install and activate the plugin.
  2. Go to Donor Merchant Settings Payments and paste your Stripe and/or PayPal API keys.
  3. Add the Donation Form block (or [donor_merchant]) to any page.
  4. Test with Stripe test mode, then switch to live.

Screenshots

Blocks

This plugin provides 1 block.

  • Donation Form

FAQ

Is recurring giving really free?

Yes. Monthly, quarterly, and yearly donations via Stripe are part of the free plugin. You pay only the payment processor’s own fees.

Do I need an SSL certificate?

Yes. Stripe and PayPal require your site to be served over HTTPS.

Where do I find my Stripe API keys?

In your Stripe dashboard under Developers API keys. Use the test keys with Test mode to try things out safely.

Does the plugin store card numbers?

No, never. Card details are entered into fields hosted by Stripe (or on PayPal’s site) and never reach your server.

Does the plugin run background tasks or phone home?

No. Donor Merchant sends no telemetry and performs no analytics, tracking, or license checks. It never contacts our servers.

When a page loads and nobody is donating, the plugin makes no external or background server requests. The donation form and the goal thermometer render entirely from your own database, and there is no AJAX polling or heartbeat activity.

On pages that contain the donation form, the visitor’s browser loads Stripe.js and/or the PayPal SDK from the gateway’s own CDN. That is required so card details go straight into the gateway’s secure fields and never touch your server. No donor or payment data is sent until someone submits, and on pages without a form those scripts are not loaded at all.

Your server contacts Stripe or PayPal only when a visitor submits a donation, when a gateway sends an incoming signature-verified webhook, when a donor manages a recurring gift in the donor portal, and, if you enable them, when an outgoing webhook or Mailchimp sync fires on a completed donation.

The plugin schedules two WordPress cron events, and neither makes an external request. The first runs daily and, on the first of the month, emails the fundraising summary to your own admin address if that option is enabled. The second also runs daily and deletes donation attempts that were never completed and have sat unfinished or failed for more than 90 days; completed and refunded records, and anything attached to a recurring gift, are never touched. Filter donor_merchant_stale_donation_days to change the window, or set it to 0 to switch the cleanup off.

Can donors cover the processing fees?

Yes – enable fee recovery in settings and donors can opt in to add the estimated processing fee to their gift.

Are webhooks required?

Recommended, but not required for one-time gifts. Recurring renewal payments are recorded via the Stripe webhook, so set it up if you accept recurring gifts. The settings page shows the exact URL and events to use.

Does it work with my theme?

The form ships with clean, self-contained styling that adapts to any well-built theme, and everything is namespaced to avoid conflicts.

Reviews

There are no reviews for this plugin.

Contributors & Developers

“Donor Merchant” is open source software. The following people have contributed to this plugin.

Contributors

Translate “Donor Merchant” into your language.

Interested in development?

Browse the code, check out the SVN repository, or subscribe to the development log by RSS.

Changelog

2.5.3

Fixes

  • Fix: the “Add donation” button on the Donations screen led to “Sorry, you are not allowed to access this page.” The screen was registered and then immediately removed from the menu to keep it hidden, but WordPress works out which screen to load by looking the page up in the menu, so removing it left the screen unreachable. It is now hidden after the page has loaded instead, and the screen opens normally.
  • Fix: rate limiting grouped IPv6 donors by the first half of their address. On servers that report IPv4 visitors in IPv6 form, which is common, that half is all zeroes, so every donor on the site shared a single allowance and legitimate gifts were turned away once it ran out. Those addresses are now recognised and counted individually.
  • Fix: the rate limit window kept moving. Each allowed request pushed the expiry back another hour, so instead of allowing 15 attempts per hour it allowed 15 in total and then refused everything until a full hour had passed with no activity at all. The window is now a real hour. This was felt most by donors sharing an address, such as a church office, a school, or a mobile network.

New

  • New: add a donor by hand from the Donors screen, for a pledge, a prospect, or a record you are moving across yourself. If the email address is already on file, that donor is updated rather than duplicated.
  • New: shortcodes have their own Settings tab, listing each one with a usage example, a copy button, and your campaign IDs so you can copy a shortcode that already works on your site.
  • New: abandoned donation attempts are cleaned up automatically. Every started donation records a row before the payment is attempted, and forms that were never completed used to stay forever. Attempts still unfinished or failed after 90 days are now removed once a day. Completed and refunded records are never touched, and neither is anything attached to a recurring gift.

Payments

  • Stripe: your donor’s name and email address are now passed to Stripe with the payment, which improves Stripe’s own fraud scoring, and the card form no longer asks for them a second time.

Spam and abuse

  • New installs now default to a five dollar minimum donation rather than one dollar. Card testers favour the smallest amount a form will accept, and card networks advise setting a floor appropriate to real donors. Existing sites keep whatever minimum they already set.
  • The donation form now rejects submissions completed impossibly fast, which catches simple automated scripts. It never rejects a slow one, so page caching is unaffected.

For developers

  • New filter donor_merchant_pre_donation rejects a donation before anything is written or any payment is attempted, so a site that needs its own spam or fraud check, including a CAPTCHA, can add one without modifying the plugin.
  • New filter donor_merchant_client_ip supplies the real visitor address on sites behind a proxy or CDN, where every donor would otherwise be counted as the same visitor.
  • New filters donor_merchant_min_fill_seconds and donor_merchant_stale_donation_days tune or switch off the two behaviours above.

Import

  • The importer now names the exports it recognises, including GiveWP, Charitable, Donorbox, PayPal, Stripe, WooCommerce, Gravity Forms and WPForms, instead of leading with a row limit.
  • The row limit per file is raised to 10,000 and is only mentioned if your file is larger, in which case it tells you how many rows it found and what to do next.

Also in this release

  • New: donations can be corrected from the Donations screen. “Mark completed”, “Mark refunded” and “Mark failed” appear next to each record, so a gift with the wrong status no longer has to be deleted to be fixed. Correcting a record never emails the donor or fires integrations.
  • Fix: a PayPal recurring donation is now recorded as completed when its first payment actually settles, not when the donor approves the agreement. Approval only authorises future billing, so a receipt could previously be sent for money that was never collected. Donors see a short “we will email your receipt when the first payment clears” message instead.
  • Fix: donor records left behind by donation forms nobody completed are now cleaned up with the attempt itself. Donors you added by hand, and donors brought in by the importer, are never removed.

Payments and records

  • Fix: Stripe subscription renewals were never recorded on newer Stripe accounts. Stripe moved where an invoice reports its subscription in its 2025-03-31 API version, and the plugin only read the old location, so from the second month onward a recurring gift was charged and no record appeared. Renewals are also now deduplicated by invoice rather than by payment intent, and take their amount from the invoice, so a gift the donor has since changed records the amount actually charged.
  • Fix: a partial refund marked the whole donation refunded, removing it from your totals, the campaign thermometer, the donor’s lifetime giving, and their annual tax statement. Only a full refund changes the status now, on both Stripe and PayPal.
  • Fix: a refunded donation could be flipped back to completed by a redelivered webhook, sending a second thank-you for money that had been returned.
  • Fix: PayPal gifts paid from a bank account (eCheck), or held by PayPal for review, stayed pending for ever. The plugin now handles PayPal’s capture, refund and reversal events, so those gifts settle when the funds clear.
  • Fix: a PayPal recurring donation is now attached to its record from PayPal’s own notification. Previously this depended on the donor’s browser completing one last request, and if that was interrupted PayPal kept billing while the site recorded nothing.
  • Fix: bank (ACH) donations are recorded as processing while the money moves, instead of sitting at pending until a webhook arrived. Sites without a webhook configured left those gifts unrecorded indefinitely.
  • Fix: if a donation could not be saved, the payment is no longer attempted. Previously the card could be charged with nothing on file to match it to.
  • Fix: donation amounts in currencies without decimal places, such as JPY, are no longer stored with fractions that were never charged.
  • Security: the PayPal activation endpoint no longer reveals whether a donation exists or has completed before the caller proves the subscription belongs to it.
  • Security: the plugin no longer ships an unused contact endpoint that could send mail.

Donor records

  • Fix: importing a CSV without name or phone columns erased those details on every donor it matched. Blank values can no longer overwrite what is already on file, on any screen.
  • Fix: the importer treated a transaction ID as advisory, so two genuine same-day gifts of the same amount were merged into one even when each had its own ID. It also read European amounts incorrectly, importing “50,00” as 5000.00.
  • Fix: the importer now tells you when a file is too large to carry between the two steps, instead of returning silently to the upload screen.

Donation form

  • Fix: changing the amount or frequency while a payment was being prepared could charge the previous choice, including enrolling a donor in a recurring gift after they had switched back to one-time.
  • Fix: a successful payment whose confirmation request failed showed the donor an error under a live Donate button, inviting a second gift. They now see the thank-you, and the webhook reconciles the record.
  • Fix: changing the gift at the wrong moment could leave the Donate button permanently disabled, or show a technical error to a donor who had already paid.
  • Fix: PayPal errors now show PayPal’s own explanation rather than a generic message, and cancelling the PayPal window returns the form to normal.
  • Accessibility: form messages are announced to screen readers reliably, and focus moves to a validation error rather than leaving the reader on the button.

Other

  • The donor portal now shows only recurring gifts that have actually been paid, is marked as uncacheable so a page cache cannot serve one donor’s history to another, and picks up your chosen accent color and button shape.
  • Annual statements covering more than one currency no longer show a combined total that matched none of the gifts listed.
  • A refunded donation’s receipt link is now clearly marked as refunded rather than serving a full-value receipt.
  • The monthly summary is sent even if the site had no traffic on the 1st, rather than skipping that month entirely.
  • On multisite, choosing to delete data on uninstall now clears every site, and removes the plugin’s temporary data as well.

Forms

  • Changed: the Button shape setting now also shapes the giving frequency, donation amount, and payment method buttons, not just the donate button. Choosing Sharp used to produce a square donate button underneath fully rounded amount buttons. On existing sites the amount buttons will look slightly less rounded after updating, because they now follow the Rounded setting instead of always being fully round. Choose Pill to keep the previous look.
  • Fix: on the Forms tab, the ring marking the selected accent color stayed on whichever color was saved and never moved when you picked a different one. Picking a color also cleared the ring entirely.
  • Fix: the live preview on the Forms tab ignored your saved settings until you changed something, so it always opened showing the default blue and the default button shape.
  • Changed: fund options are now entered one at a time and shown as removable tags, rather than as free text in a box asking for one per line.

Settings

  • Settings: Stripe and PayPal are now clearly separate panels on the Payments tab, each with its own heading and a badge showing whether it is connected, so it is obvious which gateway a field belongs to. The Email and Integrations tabs are organised the same way, with one panel per outgoing email and per integration.
  • Settings: each key field now shows the format it expects, such as pk_live_... or whsec_..., so a secret key cannot be pasted into a publishable key field by mistake.
  • Settings: clicking a shortcode anywhere in Settings now selects the whole thing, including the brackets, ready to copy.
  • Settings: added hoverable help icons to the fields that most often cause confusion, including the webhook secrets, the fee estimate values, the minimum donation amount, and the tax ID.
  • Settings: the uninstall option now shows a plain warning describing exactly what is deleted, that deactivating the plugin does not trigger it, and that you should export your records first.
  • Settings: corrected the Advanced tab, which still referred to a legacy shortcode that was removed in 2.4.0.
  • Settings: tidied the Forms tab wording, switched to US spelling, and removed the hyphens from the thank you page label and its dropdown.
  • Settings: the custom fields table now starts with one empty row, so it is clear you can type into it, and each row’s remove control is a compact icon rather than a text link.

2.5.2

  • Fix: in the donation form, a dropdown rendered a few pixels taller than the text field beside it in the same row, because inputs and selects were using different line heights. All form controls now share one line height and match exactly.
  • The optional dedication section now uses a solid border rather than a dashed one, which reads as a real part of the form instead of a placeholder, and its heading and field labels have been shortened.

2.5.1

  • Security: the public donation endpoint no longer reuses a donor’s stored Stripe customer. Every recurring checkout creates its own customer, so knowing a donor’s email address cannot attach a subscription to their Stripe record.
  • Security: PayPal subscription activation now verifies the subscription uses the plan this site priced for that donation, so a cheaper plan cannot settle a larger donation record.
  • Security: an existing donor’s name and phone can no longer be rewritten through the public donation endpoint. Blank fields are still filled in.
  • Security: Stripe webhook events are bound to this install and to the stored payment reference, so an event from another site sharing the same Stripe account cannot complete a local donation.
  • Fix: a PayPal order is only recorded as paid when the capture itself is COMPLETED. A PENDING capture (eCheck or review) now stays pending until it settles.
  • Fix: donation completion is a single atomic update, so simultaneous verify and webhook calls can no longer send duplicate receipts or fire integrations twice.
  • Fix: the fee recovery line showed “$NaN” instead of the estimated fee. Numeric settings passed to the front end arrive as strings, which turned the fee calculation into string concatenation; they are now coerced to numbers and fall back to zero if a setting is empty or invalid.
  • Fix: in the donation form, fields inside the optional dedication section could overflow their column and overlap the field beside them. The form set box-sizing by inheritance, and that chain did not hold inside the collapsible section, so those controls added their padding on top of their width. Box sizing is now stated outright.
  • Fix: dropdowns in the donation form now match the height and width of the text fields beside them instead of keeping their browser default appearance.
  • Hardening: rate limiting groups IPv6 clients by /64 rather than by single address, and the PayPal capture and activate endpoints are now throttled.

2.5.0

  • New: Import donation history from another plugin or platform. Upload a CSV exported from GiveWP, Charitable, Donorbox, Givebutter, or a spreadsheet; columns are detected automatically and can be corrected before anything is written. Preview the result first, and re-run safely: gifts already on file are detected by transaction ID, or by matching donor, amount, and date.
  • Imported donations are written silently. Historical records never trigger receipt emails, admin notifications, outgoing webhooks, or Mailchimp syncs.

2.4.1

  • Sanitization: every register_setting() call now declares an explicit sanitize_callback. Secret fields use sanitize_text_field and preserve their stored value on a blank save through a pre_update_option filter (WordPress.org review follow-up).

2.4.0

  • Form style customization: accent colour swatches with a live preview, plus a button-shape control (pill / rounded / sharp).
  • Contact form REST endpoint for the marketing site (rate-limited, honeypot-protected).
  • Security: payment secret keys are no longer echoed back into the settings page; secret fields render blank and keep their stored value on save.
  • Compliance: all admin scripts and the receipt stylesheet are now loaded through the enqueue API; all custom option, transient, and shortcode names use the donor_merchant prefix; every database query is bound through wpdb::prepare() with the %i identifier placeholder.
  • Requires at least: WordPress 6.2 (for the %i placeholder in wpdb::prepare()).

2.3.1

  • Security: CSV exports now neutralize spreadsheet formula injection (donor-supplied values beginning with =, +, -, or @ are escaped).
  • Security: PayPal subscription activation now strictly requires the subscription’s custom_id to match the donation, removing an edge-case bypass.

2.3.0

  • New: Monthly fundraising summary email – totals, new donors, recurring count, and top campaign, sent on the first of each month (with a “send a sample now” button).
  • Docs: documented the developer hooks (actions, filters) and the signed outgoing-webhook payload.

2.2.0

  • New: Bank debit (ACH) payments through Stripe – lower fees than cards.
  • New: Recurring PayPal donations via PayPal Subscriptions, with a webhook for renewals.
  • New: Designations / funds – donors choose which fund their gift supports.
  • New: Integrations – outgoing webhooks (Zapier/Make) signed with HMAC, and Mailchimp audience sync with an opt-in checkbox.
  • New: Custom form fields (text, paragraph, dropdown, checkbox), saved with each donation and included in exports.
  • Improved: Donations CSV now includes designation and custom-field columns; admin notifications list them too.
  • Developer: webhook payload includes campaign, designation, and custom fields.

2.1.0

  • New: Campaigns – multiple donation forms, each with its own goal, suggested amounts, and description, plus a live goal thermometer ([donor_merchant campaign="N"] and [donor_merchant_progress campaign="N"]).
  • New: Donor self-service portal – passwordless magic-link dashboard for giving history, receipts, and managing recurring gifts through Stripe’s billing portal ([donor_merchant_portal]).
  • New: Printable receipts (save as PDF) for every donation, plus year-end annual tax statements, served securely over signed links.
  • New: Offline / manual donation entry from the admin (checks, cash, bank, other).
  • New: Tax-receipt settings (Tax ID/EIN, organization address, tax-deductible language) and a donor-portal page setting.
  • Improved: Donations CSV export now includes the campaign column.
  • Developer: donor_merchant_suppress_receipt and donor_merchant_suppress_admin_notification filters.

2.0.0

  • Complete rewrite for production.
  • Stripe Payment Element with one-time and recurring (monthly/quarterly/yearly) donations.
  • PayPal Smart Buttons for one-time donations.
  • Fee recovery, tributes/memorials, and donor notes.
  • Donor and donation management with search, filters, and CSV export.
  • Customizable email receipts and admin notifications.
  • Stripe webhooks with signature verification (renewals, refunds, failures).
  • Reports dashboard.
  • Donation Form block for the editor; legacy [donation_form] shortcode still supported.
  • Internationalization-ready; 20 currencies.

1.0

  • Initial prototype.