PayArc Payments
PayArc payment gateway for WordPress: Gravity Forms, GiveWP and WooCommerce (Hosted Fields, Apple Pay, saved cards, site-managed recurring, card-testing protection)
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/netzih/payarc-wordpress/archive/refs/heads/main.zipPayArc Payments for WordPress
One plugin, one PayArc account, three integrations: Gravity Forms, GiveWP and WooCommerce. Card details are entered in PayArc's Hosted Fields (one PayArc iframe per field). This site only ever handles single-use card tokens and saved-card references.
It is a fork of usaepay-wordpress, with every name, option, hook, meta key
and gateway id renamed, so both plugins can be active on one site. It is
built on chabadrichmond/payarc-php, the framework-free
PayArc client. That library is kept in lib/payarc-php as a git subtree:
update it with
git subtree pull --prefix lib/payarc-php ~/dev/payarc-php main --squash
Status: all three modules pass their sandbox runs end to end (see Tested). Nothing has been charged in live mode yet.
Installation
Build the zip with bin/build-zip.sh, then go to Plugins > Add New Plugin >
Upload Plugin. The zip carries the bundled client, so the site needs no
Composer. Requirements: WordPress 6.4+, PHP 8.1+ with curl and json, and at
least one of Gravity Forms 2.9+, GiveWP 4 or WooCommerce 8 (WooCommerce
Subscriptions for recurring WooCommerce payments).
Setup
- Open Settings > PayArc and choose Sandbox or Live.
- From the PayArc dashboard (API, then the eye icon), enter for that mode:
- the API bearer token: secret, server-side only. A blank field keeps the stored token.
- the Client ID: public, used by the card fields in the browser.
- Press Check credentials. It lists one charge with the bearer token and opens an unused card-field session with the Client ID. Nothing is charged. A wrong Client ID is reported as such (the portal answers 403).
- Optionally tick Apple Pay / Google Pay. The wallet sheet opens in a small PayArc window, so this site needs no Apple merchant setup or domain file. Wallets are offered only for one-time payments, because their tokens cannot be charged again.
For a sandbox account, create one at
https://testportal.payarc.net/accounts/create/test. Test card: 4012 0000
9876 5439, expiry 12/29, CVV 999, ZIP 85284. Mastercard 5146 3150 0000 0055
(CVV 998) and Discover 6011 0009 9302 6909 (CVV 996) work too. The sandbox
now and then declines a charge with D2026 ("Do not honor"); the same card a
minute later passes.
Additional accounts, turning modules off, plugins built on this one
These work as in the USAePay plugin, with payarc names:
- Accounts:
Settings::accounts(), and the optional account argument ofbearerToken(),clientId(),hasApiCredentials(),isConfigured()andGateway::client(). - Filters:
payarc_payments_modules,payarc_payments_unresolved,payarc_payments_renewal_workers,payarc_payments_orderid_prefix. - Browser helper:
window.PayarcHostedFieldswith{mount, tokenize, wallets, errorText}.
How PayArc differs from USAePay (and what the plugin does about it)
- Saved cards. PayArc keeps cards under customer records. Saving a card
creates one PayArc customer and attaches the token; the saved-card
reference is
customer_id:card_id. A token can be used once, so a card that must be kept is saved first and the saved card is charged, including a subscription's first payment. That also proves at signup that renewals can be charged. Saved cards charge without a CVV (verified with server and browser tokens).Gateway::saveCard()remembers the saved card per token, so a resubmitted form does not fail on the used token. - No PayArc receipts. PayArc emails and texts the payer unless told not
to. Every charge and refund sends
do_not_send_email_to_customer/do_not_send_sms_to_customer. The payer's email goes in metadata, not the top-level field. - Refunds of unsettled charges. PayArc does not refuse them as its docs
say. It voids the whole charge, whatever amount was asked, and records
only the asked amount in
amount_voided(verified in the sandbox). So a full refund of an unsettled charge is sent (and reported as a void), and a partial refund is sent only for a charge known to have settled. Otherwise staff are told to refund in full or wait for settlement. PayArc also requires a refund description of at least five characters; short ones are prefixed. - Declines come either as an HTTP error or as a 2xx charge with
failure_codeset (status "Declined"). Both are read byCharge::outcome(). "Duplicate request (approved previously)" codes and unknown statuses are treated as ambiguous, never as declines.
Charge at most once
Every charge and refund is still guarded by a marker (orderid, key, time,
amount) stored before the request, under a lock on the orderid. What is new
is the key: it is sent as PayArc's Idempotency-Key, and PayArc answers
a repeated key with the original result, whatever the new body says. So:
- A lost answer is resent at once with the same key.
- A recent marker (under an hour,
Reconcile::REPLAY_WINDOW) is replayed with its key. An earlier charge comes back instead of a second one. - An older marker is looked up by its reference in the charge list
(metadata
reference). - A refund marker carries a snapshot of the sale (refunded amount, remaining amount). The sale is compared with it, which settles the refund at any age.
- A definitive answer (a decline or a refusal) clears the marker, so the next attempt gets a new key. PayArc would otherwise replay the old decline.
One-off keys are the orderid plus a short random suffix. Renewal keys are deterministic, one per record, installment and attempt, so a crashed cron run gets the same charge back. This was verified: a replayed Gravity Forms installment returned the earlier charge, made no new charge at PayArc, and counted nothing twice.
Markers are stored in:
- WooCommerce order meta:
_payarc_charge_sent,_payarc_refund_sent,_payarc_renewal_pending. - GiveWP donation meta:
_payarc_charge_sent,_payarc_refund_sent, and the optionpayarc_give_renewal_state. - Gravity Forms entry meta:
payarc_reconcile_*,payarc_refund_sent. wp_optionsrowspayarc_marker_*, for submissions without an entry and for saved cards per token. These are purged after seven days.
Settings > PayArc > Unresolved requests lists every open marker with a "Check at PayArc" action.
Card form
The four PayArc fields (card number, MM/YY, CVV, ZIP) are drawn as one box
with thin dividers, like a single card input, in every module; on a phone
they stack into two rows. The box gets a focus ring while any field has
focus, and turns red when a field is invalid. The border lives in
assets/css/payarc-payments.css, and the borderless inputs inside the
iframes in DEFAULT_CSS in assets/js/payarc-hostedfields.js. Browsers do
not match :focus-within while focus is inside a cross-origin iframe, so the
script sets payarc-focused on the box instead.
The CSS for inside the iframes sits in a `