Square payment processor extension for CiviCRM.
- Extension key:
org.civicrm.square - Version: 1.2.0 (beta)
- CiviCRM compatibility: 6.16+
- License: AGPL-3.0-or-later
- Square API version: 2025-10-16 (sent by the bundled
square/square43.2 SDK) - Requires:
mjwshared,civi_contribute(seeinfo.xml)
This extension ships its vendor/ directory (and composer.lock) committed to the
repository, so no composer install step is required after cloning/downloading —
just enable the extension as usual (Administer → System Settings → Extensions).
- One-time card payments via Square Payments API (
/v2/payments) - Recurring contributions via Square Subscriptions API (
/v2/subscriptions) - Refunds via Square Refunds API (
/v2/refunds), recorded in CiviCRM once Square completes them - Subscription cancellation and amount changes synced to Square, and cancellations made in Square synced back to CiviCRM
- Square Web Payments SDK for browser-side card tokenization — card details never pass through CiviCRM
- Card form ZIP/postal code kept in step with the billing address's, so Square never sees two different ones
- Card-on-file support through CiviCRM PaymentToken
- Square customer creation and deduplication (by
reference_id, then email — a customer already mapped to another contact, e.g. a family member sharing the email, is never shared) - Buyer verification (Strong Customer Authentication) during card tokenization
- Webhook event handling with deduplication and delivery logging
- Supports sandbox (test) and production environments
- Currency-aware amounts: Square amounts are in each currency's smallest unit, so JPY (which has no cents) is never multiplied by 100
Navigate to Administer → System Settings → Payment Processors and create a new processor of type Square.
| Field | Description |
|---|---|
| Square Application ID | Found under Developer Dashboard → Your Application → Credentials |
| Square Access Token | Live or sandbox access token |
| Square Location ID | Found under Locations in your Square Dashboard |
| Square Webhook Signature Key | Used to validate incoming webhook events (HMAC-SHA256) |
Separate sandbox credentials are supported for test mode. The processor automatically sets billing_mode = 1 (on-site) on enable to ensure compatibility with Drupal Webform CiviCRM.
| Cadence | Interval |
|---|---|
| DAILY | Every day |
| WEEKLY | Every week |
| EVERY_TWO_WEEKS | Every 2 weeks |
| THIRTY_DAYS | Every 30 days |
| SIXTY_DAYS | Every 60 days |
| NINETY_DAYS | Every 90 days |
| MONTHLY | Every month |
| EVERY_TWO_MONTHS | Every 2 months |
| QUARTERLY | Every 3 months |
| EVERY_FOUR_MONTHS | Every 4 months |
| EVERY_SIX_MONTHS | Every 6 months |
| ANNUAL | Every year |
| EVERY_TWO_YEARS | Every 2 years |
A CiviCRM frequency with no Square equivalent (for example every 3 weeks) can't be billed by Square, and is refused at checkout before any card is saved.
Recurring payments use the Square Catalog API to create subscription plans and plan variations on demand, then create a Square Subscription linked to a card-on-file. The subscription starts immediately and Square itself charges every installment, including the first — no separate charge is made at checkout. The checkout's contribution stays Pending until Square confirms that first charge by webhook, which then completes that same contribution; each later installment becomes a new contribution (via Contribution.repeattransaction) completed the same way. All payments are recorded with CiviCRM's Payment.create, and the Square payment ID becomes the contribution's and the payment's transaction ID.
Configure the Square webhook endpoint as
https://your-site.org/civicrm/payment/ipn/{processor_id}. It validates the
X-Square-Hmacsha256-Signature header before the event is queued. The queue
record is processed immediately after it is accepted. A record that fails
transiently (Square unavailable, or a related CiviCRM record not saved yet
because webhooks arrived out of order) is left in status new for the
Process Payment Processor Webhooks scheduled job to retry, for up to 72
hours; any other failure is marked error. See
docs/WEBHOOKS.md.
| Event | Action |
|---|---|
subscription.created |
Syncs subscription status to ContributionRecur |
subscription.updated |
Syncs subscription status/amount to ContributionRecur. Square has no subscription.canceled event: a cancellation arrives here with a canceled_date (the status stays ACTIVE until that date), and marks the ContributionRecur Cancelled |
invoice.created |
Nothing is recorded: the invoice is still a draft Square has not charged |
invoice.payment_made |
Records the installment: completes the checkout's Pending contribution for the first invoice, or creates and completes the next contribution of the series |
invoice.scheduled_charge_failed |
Square could not charge the card on file: marks that installment's contribution Failed (the first installment's stays Pending, since Square keeps the invoice open). Ignored if Square reports the invoice paid by the time it is processed, since webhooks can arrive out of order |
payment.updated |
Completes a one-time contribution; for a subscription payment, looks up its invoice at Square and records the installment exactly as invoice.payment_made does. A payment taken outside CiviCRM (Square Dashboard, Square Online, point of sale) is ignored unless Import Square payments made outside CiviCRM is enabled in Square Settings — and even then only at the processor's own Square location |
refund.created, refund.updated |
Records a completed refund as a negative payment against the refunded payment, including a refund made from CiviCRM that was still PENDING at Square. A refund that arrives before its payment is recorded is retried |
Webhook deduplication uses the civicrm_paymentprocessor_webhook queue (provided by the mjwshared extension)
and Square's globally unique event ID. Do not log webhook signatures, access
tokens, card nonces, or unredacted Square responses.
Square identifiers are not stored as Contact custom fields. Both are scoped per payment processor, since live and sandbox (or multiple Square merchant accounts) are entirely separate environments with their own customer/card records:
- Cards are stored as standard CiviCRM
PaymentTokenrecords (civicrm_payment_token), one per card, linked fromcivicrm_contribution_recur.payment_token_id. This lets a contact have more than one card on file across different recurring contributions, and lets other CiviCRM UI/tooling that understandsPaymentTokenwork with them normally. - Square customer IDs are stored in
square_customer_map, an extension-owned table keyed by(contact_id, payment_processor_id)— see Database Tables below.
Earlier versions of this extension stored both as a square_data custom
field group on the Contact entity. CRM_Square_Upgrader::upgrade_1000()
migrates that legacy data into square_customer_map automatically only
when exactly one live Square processor is configured (sandbox processors are
not considered: every configured processor has one). If more than one live
Square processor is configured, the legacy field never recorded which one a
given value belongs to, so migration is skipped for that data (never
guessed) and it's logged via Civi::log()->warning() for manual
reconciliation. The square_data group itself is only removed once
nothing ambiguous remains.
When contacts are merged, the removed contact's mappings move to the
contact kept (square_civicrm_merge()); where both had a Square customer on
the same processor, the kept contact's is kept. Deleting a payment processor
deletes its mappings.
js/square.js provides full browser-side integration with the Square Web Payments SDK. It supports:
- CiviCRM native contribution pages and event registration forms
- Drupal Webform (webform_civicrm module) billing blocks, including AJAX reloads
Back-office (staff-entered) card payments are not supported.
Key globals:
CRM.squarePayment— Square's namespaced integration stateCRM.vars.orgCivicrmSquare— processor settings (Application ID, Location ID, sandbox flag)window.civicrmSquareHandleReload— reinitializes the card element when the billing block is replaced via AJAX
The card element mounts into #square-card-container. Tokenization happens on form submit; the resulting nonce is written to a hidden square_payment_token field for PHP to read. The billing details on the form (name, email, address), amount and currency are passed to card.tokenize() as Square's verification details, so Square performs buyer verification (Strong Customer Authentication) as part of tokenizing — with intent CHARGE for a one-time payment and STORE for a recurring one, whose card Square's subscription charges.
The card form's ZIP/postal code and the billing address's (billing_postal_code-N) are kept in step: the card form starts with the billing postal code and is updated (via card.configure()) whenever the donor changes it, an empty billing postal code is filled in as the donor types one in the card form, and submit is stopped with an error if the two still differ.
| Hook | Purpose |
|---|---|
hook_civicrm_config |
Standard civix bootstrap |
hook_civicrm_install |
Standard civix install (schema/table creation is handled separately by CRM_Square_Upgrader, not this hook) |
hook_civicrm_uninstall |
Defensive cleanup of the legacy square_data custom field group, if it's still present |
hook_civicrm_enable |
Enforces billing_mode = 1 on all Square payment processor instances |
hook_civicrm_managed |
Wires up managed/PaymentProcessorType.mgd.php (registers the Square payment processor type) |
hook_civicrm_navigationMenu |
Adds "Square Settings" under Administer → System Settings |
| Table | Owner | Purpose |
|---|---|---|
square_customer_map |
This extension (CRM_Square_Upgrader) |
Maps (contact_id, payment_processor_id) → Square customer ID; each customer maps to one contact per processor. Created on install; dropped on uninstall. |
civicrm_payment_token |
CiviCRM core | Stores Square card-on-file references (one row per card), linked from civicrm_contribution_recur.payment_token_id. Not owned by this extension — never dropped on uninstall. |
civicrm_paymentprocessor_webhook |
mjwshared extension | Webhook queue; retains deduplication, status and retry information. Not owned by this extension. |
CRM/
Core/Payment/
Square.php Payment processor adapter (CiviCRM's CRM_Core_Payment contract)
SquareIPN.php Webhook event router and queue processing
SquareRetryableException.php Marks transient failures for webhook retry
SquareOutcomeUnknownException.php A checkout charge Square may have made without confirming it
SquareDebugLogger.php Opt-in verbose debug logging, gated by the square_ipn_debug_logging setting
Square/
Gateway.php Square API access (client, errors, idempotency keys)
Customers.php Square customers and cards on file
Subscriptions.php Catalog plans/variations; subscription changes and cancellation
Reconciler.php Webhook-driven sync into CiviCRM's ledger
Status.php CiviCRM option values and Square status mappings
Currency.php Amounts in Square's smallest currency units
Form/Settings.php Administer > System Settings > Square Settings (debug logging, external payment import)
Upgrader.php Creates/backfills/drops the square_customer_map table (see Database Tables)
js/
square.js Browser-side Square Web Payments SDK integration
managed/
PaymentProcessorType.mgd.php Registers the Square payment processor type
settings/
Square.setting.php Declares the square_ipn_debug_logging and square_import_external_payments settings
templates/
CRM/Core/Payment/Square/Card.tpl Card container HTML injected into billing block
CRM/Square/Form/Settings.tpl Settings form markup
xml/Menu/square.xml Route: civicrm/admin/setting/square -> CRM_Square_Form_Settings
square.php Extension bootstrap and hooks (config/install/uninstall/enable/managed/navigationMenu)
Use sandbox credentials first. Before accepting live payments, confirm each of the following:
- Enable the required
mjwsharedandcivi_contributeextensions, then enable this extension. - Create a Square payment processor in CiviCRM and enter the sandbox Application ID, Access Token, Location ID, and Webhook Signature Key.
- In the Square Developer Dashboard, create a webhook subscription for that
same sandbox application and set its notification URL to
https://your-site.org/civicrm/payment/ipn/{processor_id}. Replace{processor_id}with the CiviCRM payment processor ID. - Subscribe to the events in Webhook Events. Events not listed there are acknowledged but intentionally ignored.
- Verify that mjwshared's Process Payment Processor Webhooks scheduled job
is enabled. It retries queued records left in status
newby a transient failure. - Complete a test one-time contribution and, if recurring payments are in scope, a test recurring contribution. Confirm the contribution, payment token, recurring contribution, and webhook records in CiviCRM.
- Repeat the configuration with production credentials and the production Square application only after sandbox verification succeeds.
The notification URL configured in Square must be the same URL CiviCRM uses for the payment processor. Square's HMAC signature covers the full URL and raw request body; a different scheme, hostname, path, or proxy rewrite causes signature validation to fail.
The extension records webhook delivery and processing state in
civicrm_paymentprocessor_webhook. Review records with status = error and
the CiviCRM log when reconciliation does not occur.
| Symptom | Check |
|---|---|
| Webhook returns HTTP 401 | Verify the processor's Webhook Signature Key and the exact notification URL in Square. Do not trim or transform the raw request body. |
| Webhook returns HTTP 400 | Verify Square is posting valid JSON. |
| No change after a webhook | Confirm the event type is supported, the processor ID in the URL is correct, and the queue record is not in an error state. |
| Repeated webhook delivery | Look for a failed queue record and resolve its logged processing error; Square retries non-successful deliveries. |
| Card field does not appear | Confirm the selected processor is Square, on-site payment collection is enabled, and browser JavaScript is loading without Content Security Policy errors. |
| Sandbox data appears missing in live mode | Sandbox and production use different Square customers, cards, credentials, and processor configuration. |
Enable verbose webhook diagnostics at Administer → System Settings → Square Settings only while investigating an issue. Disable it after the investigation and never place access tokens, signature keys, card tokens, or unredacted API responses in tickets or logs.
Requirements: PHP 8.2 or later and the dependencies committed in vendor/.
Run these checks from the extension root before submitting a change:
vendor/bin/phpcs --standard=phpcs.xml.dist CRM square.php settings managed tests
vendor/bin/phpunit --configuration tests/phpunit/phpunit.xml.distThe codebase follows CiviCRM's two-space, Drupal-derived PHP style. The local
.editorconfig enforces whitespace basics, while phpcs.xml.dist records the
necessary CiviCRM exceptions for legacy CRM_* class names and extension hook
functions. Do not run automatic formatters across vendor/.