How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

How-to Forms

Take a payment inside a form

A donation form: a Payment element on its own last step, set to Authorize & Capture, the platform's payment script in the layout, and a receipt sent from the form's Completed event, because the platform sends none.

Context

Context. A charity wants a donation form: the donor chooses $25, $50 or $100 or types an amount, pays by card or PayPal, and gets a receipt. A form's Payment element does this without a cart or a checkout, as long as it stays a single amount from a single payer. The moment there are products, stock, shipping or calculated tax, it is a checkout instead (see Payments in forms).

Steps

  1. Make sure the payment methods exist and are active, under Invoices › Payment methods, with Test Mode on for development (see Payments).
  2. Add a last step to the donation form with one Payment element named donation: currency, amounts 25, 50 and 100, Allow Custom Amount on, the payment methods to offer, mode Authorize & Capture, and the billing address fields the receipt needs.
  3. Load the platform's payment script in the form layout, with the page's nonce (below).
  4. Create a job Form: donation → receipt on the form's Completed event, asynchronous, with a Send mail action to {$event->getRecord()->data['email']}, from the charity's address, with the receipt template below.
  5. On a development site, donate with each method's test card or account, including a declined card, and check the invoice under Invoices, the submission, and the receipt.
  6. Before launch, check that no Mock Pay method is active on the live account.

Code

In the form layout:

Template code
<script src="/assets/forms/js-payment-element.js" nonce="{$_inlineScriptNonce}" defer></script>
<script src="/assets/forms/js-payment-element.js" nonce="{{ _inlineScriptNonce }}" defer></script>

The receipt, templates/hooks/emails/donation-receipt.html. The payment's answer holds the amount as the platform formats it and the invoice id:

Template code
{$payment = $event->getRecord()->data['donation']|default:[]}
<!DOCTYPE html>
<html><body style="font-family:Arial,sans-serif;font-size:14px;color:#333333;">
  <p>Thank you for your donation of {$payment['amount']|default:''|escape}.</p>
  <p>Receipt number: {$payment['invoiceId']|default:''|escape}</p>
</body></html>
{% set payment = event.getRecord().data.donation|default({}) %}
<!DOCTYPE html>
<html><body style="font-family:Arial,sans-serif;font-size:14px;color:#333333;">
  <p>Thank you for your donation of {{ payment.amount|default('')|e }}.</p>
  <p>Receipt number: {{ payment.invoiceId|default('')|e }}</p>
</body></html>

Why this and not that

  • Authorize & Capture, never Authorize Only. Nothing in the platform captures an authorised payment later, so Authorize Only reserves the money, lets it lapse, and leaves a complete-looking submission that was never paid.
  • The receipt hangs off Completed. The submission completes only once the payment has gone through. With PayPal Donate's hosted button, that is when PayPal's confirmation arrives, minutes after the donor clicked, so never send a receipt from the result step.
  • The platform sends no receipt, and it writes no tax, whatever the element's Tax Category says. A tax-deductible receipt needs the charity's own required wording in the template.
  • The last step, on its own. The payment is attempted only once every other answer is valid, so the donor never pays and then hits a validation error. Keep any invisible captcha off this step: both take over the form's submit.
Go deeper

Was this page helpful?

Your answer goes to the docs team.