WP Manifestindependent plugin directory
manifest / ecommerce / ys-helcim-via-fluentcart

YS Helcim via FluentCart

Helcim payment gateway for FluentCart — HelcimPay.js modal & helcim.js inline card form, refunds, webhook reconciliation, Google Pay/Apple Pay, USD/CAD. English + Traditional Chinese.

by YANGSHEEP DESIGN · github.com/ya19880104/ys-helcim-via-fluentcart · website

★ 0stars
0forks

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/ya19880104/ys-helcim-via-fluentcart/archive/refs/heads/main.zip

Helcim payment integration for FluentCart with durable payment operations, remote-first refunds, and signed webhook recovery.

Stable release — 1.1.3

This is the dual-gateway v1.1.3 maintenance release: the hosted HelcimPay.js modal and the Helcim.js inline form are both registered only when their current-mode credentials and the shared durable recovery runtime are available. Every deployment must still pass the environment, browser, provider, and runtime verification gates below.

Payment methods

Payment method Collection mode v1.1.0 status
Credit card (Helcim) (ys_helcim) HelcimPay.js hosted modal; lowest PCI scope and the path for supported digital wallets Available through the durable two-phase hosted coordinator
Credit card (Helcim inline form) (ys_helcim_js) Helcim.js Verify tokenization in the browser, followed by a server-side v2 purchase Available when all current-mode credentials and recovery prerequisites are present

Both payment methods use the same safety invariants: a durable operation is claimed before the hosted session can be exposed or an inline purchase can be sent, provider success requires a valid transaction ID and exact proof, and a lost response remains reconcilable without creating a second charge.

Requirements

Item Requirement
WordPress 6.0 or later
FluentCart 1.5.2 or later
PHP 8.1 or later
Currency USD or CAD
Database Transaction-safe InnoDB tables for FluentCart and the plugin operation tables
HTTPS Required for inline card entry and webhook delivery
Scheduled jobs Working WordPress Cron or an equivalent server cron that runs due WordPress events at least once per minute
Helcim A production account or dedicated Developer Test Account with the required API, Helcim.js, and webhook credentials; the API token must grant Transaction Processing Admin and permit GET /card-transactions

The plugin fails closed when FluentCart is unavailable, storage is not transaction-safe, required credentials are missing, or its recovery event cannot be scheduled. Site operators must separately prove that WordPress Cron is actually being executed.

Installation

  1. Install the release ZIP so WordPress creates /wp-content/plugins/ys-helcim-via-fluentcart/.
  2. Activate YS Helcim via FluentCart while FluentCart is active.
  3. Confirm the operation, outbox, webhook receipt, and refund-resolution tables were created successfully.
  4. Set the FluentCart store currency to USD or CAD.
  5. Configure the credentials belonging to the store's current Order Mode and prove that the hosted API token can read GET /card-transactions.
  6. Configure the mandatory clean webhook route and verify WordPress Cron before enabling checkout.

Test and live credential isolation

FluentCart's global Order Mode selects the credential set:

  • test uses only the Developer Test Account credentials from the Test tab.
  • live uses only the production account credentials from the Live tab.
  • A transaction keeps its original mode. Refund and reconciliation operations resolve credentials for that recorded mode rather than silently switching to the store's current mode.
  • Never copy a test account's verifier, API token, Helcim.js token, or secret into a live credential slot.

Secret fields are encrypted with FluentCart's key helpers before storage. A missing or corrupt encrypted value is treated as unavailable.

Inline Helcim.js configuration

The inline gateway requires all four values for the active mode:

  1. API Token with Transaction Processing Admin access. Purchase alone is insufficient because refund and reverse also use this token.
  2. Helcim.js Token from a Helcim.js configuration created as Card Verify / Tokenize Only.
  3. Helcim.js Secret Key from the same configuration.
  4. Webhook Verifier Token belonging to the same Helcim account and mode.

The Helcim.js configuration must be Active, use the matching currency and terminal, include the checkout site's exact HTTPS origin under Website URLs, and have Include XML on Response enabled. The browser sends only the returned xml and xmlHash proof envelope to WordPress. WordPress verifies Helcim's keyed full-XML proof with the matching Secret Key, extracts the card token only from that authenticated XML, and then calls the v2 payment/purchase endpoint. The v2 purchase transaction ID—not the Verify ID—is the refundable provider identifier stored for the FluentCart transaction.

Developer Test Account behavior

Use Helcim's official test cards only with a dedicated Developer Test Account. The account's terminal enforces its test status. Keep the legacy Helcim.js Configuration's Test Mode off.

The inline Verify-to-v2-purchase flow intentionally omits the legacy Helcim.js test=1 field. Sending that flag can produce a demonstration token that the v2 Payment API rejects as unverified. FluentCart Order Mode still selects the test credential set; it does not turn the deprecated SDK flag back on.

Hosted HelcimPay.js configuration and two-phase flow

The hosted gateway requires the active mode's API Token and Webhook Verifier Token. The API Access configuration must enable the checkout integration, grant Transaction Processing Admin, and permit Card Transaction reads through GET /card-transactions; the latter is required to recover a lost browser callback. Hosted checkout fails closed if that read capability or the recurring recovery schedule cannot be proven. It provides the lowest PCI scope and supports Helcim-hosted payment experiences such as eligible digital wallets.

Hosted checkout uses a durable two-phase boundary:

  1. The server reloads the exact FluentCart charge identity, creates a purchase operation with a one-time confirmation token, atomically claims its active scope, and reads the claim back before calling helcim-pay/initialize.
  2. The operation UUID is the provider correlation value. The modal is exposed only after the initializer returns an exact checkout/secret-token pair for that claimed operation; one-time verification material is stored encrypted.
  3. Browser callback data is treated as untrusted. Confirmation reloads the exact hosted charge and operation, consumes the short-lived confirmation token, verifies the provider hash with the one-time secret, and requires the exact operation correlation.
  4. Approval or decline proof must match the original status/type/amount/currency identity; an approval also requires a valid positive v2 transaction ID. Only durably persisted proof may drive FluentCart payment effects.
  5. Replays resume an already persisted success idempotently. A lost browser response is reconciled through the signed clean webhook and API proof without opening a second active payment attempt.

If the journal cannot be created, claimed, or read back, no hosted session is shown. If initialization fails and that failure cannot be durably recorded, the scope remains locked for reconciliation instead of inviting another charge.

Hosted and Inline lost-response recovery

When the browser callback is lost, the one-minute recovery worker begins provider lookup after the operation is five minutes old. During the early safety window, only one exact approved result bound to that operation can resolve the remote state; persisted success resumes local completion idempotently, and recovery never sends another purchase.

  • An empty provider collection inside the checkout's validity window is never proof that no charge occurred and never releases the active payment scope.
  • An empty or declined observation before the 70-minute checkout-material safety boundary (Helcim's 60-minute token life plus a 10-minute indexing grace) does not clear the modal metadata or unlock another payment attempt.
  • After that safety boundary, one exact declined result resolves the operation as declined.
  • After that safety boundary the payment window can no longer charge and any earlier charge has long been indexed by invoice number, so two consecutive authenticated empty reads close the abandoned checkout as canceled, free the transaction scope, and add an order note. The order stays unpaid, matching FluentCart's own gateways, and the shopper can pay again. Exact late approval of a closed checkout still binds; if the order was meanwhile paid by another charge, the conflict is persisted as a provider-ID mismatch for administrator review. The recovery sweep closes expired windows even after automatic lookups paused, and releases legacy canceled rows that still hold the scope.
  • The automatic provider-lookup phase is bounded to seven claimed attempts with persisted backoff. If no exact proof is available, automatic recovery pauses while the operation and active scope remain locked.
  • A paused or charged-but-locally-incomplete operation appears in a manage_options WordPress admin notice. An administrator may use Check Helcim once for one nonce-protected lookup; this does not reset the automatic retry budget, and an inconclusive result remains locked. A closed abandoned checkout is not shown there.
  • Paused operations are intentionally retained rather than auto-deleted or auto-failed. Monitor the admin notice until exact signed-webhook/provider evidence or a conclusive Check Helcim once result resolves the operation.
  • Retained rows can accumulate, so operators should monitor their count and age. A row that remains after seven attempts is an expected fail-closed state, not by itself evidence that WordPress Cron stopped; expired encrypted checkout material is purged while the audit row and transaction-scoped lock remain.
  • The lock applies only to the original FluentCart transaction. It prevents another charge for that unresolved transaction without disabling the gateway or blocking a different new transaction.
  • A fresh attempt for the same transaction is allowed only after no-charge evidence: declined, a never-sent failed, a pre-provider expired state, or a canceled checkout closed after the safety boundary.

Uncertain results in the browser

When a checkout page cannot trust its confirmation (a Helcim webhook finished the same payment first, the response was lost, or a hosted window reported no usable result), it asks the read-only ys_helcim_fct_payment_status endpoint, authorized by a transaction-bound status token issued with the order. A paid order redirects to the receipt; a transaction with no journaled charge attempt, or a declined, never-sent, or closed-expired attempt, lets the shopper try again; anything still unresolved keeps the page locked with a "do not pay again" message after about two and a half minutes of checks. The endpoint never contacts Helcim and never changes state.

  • A successfully applied purchase keeps its transaction-family reservation permanently. This is intentional: even if another request read an empty family immediately before the successful row was inserted, the database UNIQUE reservation still prevents that stale request from opening a second provider session. Refund and reverse scopes continue to release after their local effects are applied.

Before enabling hosted checkout on each test/live credential set, confirm that a harmless filtered GET /card-transactions request succeeds and returns the documented root JSON list. A 401, 403, timeout, malformed envelope, or missing recurring event disables new hosted checkout rather than weakening recovery.

The recurring event stored by WordPress is only a schedule record; it does not prove that a process is executing due events. If DISABLE_WP_CRON is enabled, configure and monitor an external runner that invokes WordPress cron at least once per minute. Verify that the Helcim events advance and that a deliberately due recovery operation is claimed before treating hosted recovery as available.

Card declines

A declined card never leaves the shopper guessing. Helcim's decline text is mapped to one actionable message (check the security code, the card has expired, the card number is not valid, not enough funds, the billing address does not match, try again in a moment, contact your bank, or contact the store), and every message states that no payment was taken. Fraud screening and lost/stolen reasons are only ever shown as a bank decline. When Helcim returns its reason to the server (inline purchases and hash-verified hosted results), the reason (with markup removed and card-like numbers masked) and the declined transaction ID are written to the order activity log as "Helcim declined a payment attempt".

Mandatory clean webhook

The v1.1.0 recovery design requires a signed webhook for every enabled current-mode payment path. Settings validation and checkout for both hosted and inline payments fail closed when the current-mode verifier is missing.

The delivery route is:

https://payments.example.com/wp-json/ys-fc-pay/v1/events/card

Requirements:

  • HTTPS only.
  • The complete hostname and path must not contain the provider name.
  • Use the exact REST path /wp-json/ys-fc-pay/v1/events/card; the legacy FluentCart query listener is retired and returns 410.
  • If the WordPress site's normal hostname contains the prohibited term, use a neutral HTTPS alias or a narrowly scoped reverse proxy that forwards only this POST route.
  • Store separate test/live verifier tokens when the accounts differ.
  • Do not reuse a verifier from another site or environment without proving account ownership and event scope.

On receipt, the plugin verifies the signed timestamp/body, rejects stale or replayed deliveries, records a durable receipt, fetches the transaction from the API with the credential for the candidate mode, and binds the provider event to one durable payment attempt before changing FluentCart state.

Refund and void events for payments taken by this plugin, such as a void made in the Helcim dashboard, go through the same checks and are recorded as described in Refunds and voids made in Helcim.

Durable purchase behavior

Both payment flows use a persistent operation journal and an active-scope lock:

  1. FluentCart creates the order and charge transaction.
  2. The plugin creates or reuses the durable operation for that exact payment attempt.
  3. A one-time confirmation token authorizes the public confirm request.
  4. The server claims and verifies the operation before exposing a hosted modal or sending the inline provider purchase with its persisted idempotency key.
  5. Approved provider proof is recorded before local payment effects are finalized.
  6. Terminal declines release the payment scope; indeterminate transport/provider outcomes retain the scope for reconciliation.

Do not tell a shopper to submit again while an operation is indeterminate. Resolve it from provider evidence or the signed webhook first.

Remote-first refunds

FluentCart 1.5.2's native refund service records a local refund before calling the gateway and can leave a false local refund when the provider fails. This plugin therefore vetoes the native Helcim refund path and provides a dedicated FluentCart → Helcim Refunds workflow.

On a FluentCart order page, Refund opens the same Helcim refund panel in a dialog without leaving the order; closing the dialog after a refund, void or sync reloads the order so its payment status is current. The Helcim Refunds page remains available for looking up any order by its number, and Ctrl-click or middle-click on the order page's Helcim refund action opens it in a new tab.

The replacement flow is remote-first:

  1. Load and lock the refundable Helcim parent transaction.
  2. Validate amount, currency, mode, remaining refundable total, and historical integrity.
  3. Persist a deterministic 36-character provider-safe idempotency key.
  4. Call the Helcim refund API.
  5. Only after exact provider success, atomically record the FluentCart refund and its local effects.
  6. Persist recoverable local effects in the outbox; never send another provider refund merely because a local effect must be retried.

For a full refund against a proven open batch, the narrow reverse fallback is allowed only after the original transaction and batch response prove the same approved purchase, amount, currency, batch ID, and closed=false. Refund and reverse are separate journaled operations with separate persisted keys.

In practice this means a same-day payment that Helcim has not settled yet can be cancelled from the panel: enter the full amount and the plugin sends a void (Helcim "reverse"), which carries no processing fee and clears the pending charge from the card within one to two days. Helcim refuses partial refunds until the batch settles (it settles once a day), and the panel says so without moving any money. Prefer cancelling through this panel; a void or refund made directly in the Helcim dashboard is recorded back in FluentCart as described below.

An indeterminate refund remains locked for reconciliation. The positive-resolution UI requires fresh provider proof, an explicit candidate transaction ID, operator attestation, and an exact confirmation phrase. The candidate must carry the same Helcim invoice number as the original payment, and Helcim's transaction list for that invoice is checked with the same rules as the Helcim sync, so a refund of another order or a refund Helcim later voided is rejected. The panel shows the candidate's type, amount and invoice number before you attest. It never converts an unknown outcome to failure merely to permit another refund.

Refunds and voids made in Helcim

A refund or void made directly in the Helcim dashboard is recorded in FluentCart from Helcim's own transaction records: automatically when the signed webhook delivers it, or on demand with Sync refunds from Helcim in the Helcim Refunds panel. A record is written only when it matches the original payment's order, account, remaining refundable amount, and FluentCart refund accounting. A void when Helcim also lists a refund for the same payment, a refund that was later voided, or anything else that does not line up is reported for review instead of changing the order, and a Helcim transaction list too long to check completely is retried later. If Helcim has completed a refund that FluentCart could not finish recording, new refunds for that order stay blocked and an administrator notice explains the next step. Orders paid before the plugin kept its payment journal cannot be synced automatically; compare them with Helcim before refunding.

WordPress Cron requirement

Durable local effects and stale-claim recovery depend on scheduled events:

  • A per-operation event retries incomplete local refund effects.
  • A bounded one-minute sweep recovers abandoned claims and processes ready outbox rows.
  • The same one-minute cadence independently claims due Hosted and Inline lost-response operations, applies persisted provider success locally, and schedules bounded provider lookups with durable backoff.
  • Plugin preflight fails closed when the recurring recovery events cannot be installed or repaired; both checkout methods also prove that the recovery event is healthy and that the current API token can perform the required read-only card-transaction lookup.

On low-traffic or production sites, configure a real server cron to run due WordPress events at least once per minute. If DISABLE_WP_CRON is enabled, an external scheduler is mandatory. Monitor the event queue and investigate repeated outbox errors; do not delete journal rows to make a report appear clean.

Release package

Build from the repository root with Windows PowerShell 5.1 or newer and PHP 8.1 CLI available. The builder refuses to package mismatched checkout translation keys or stale POT/PO/MO catalogs:

powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\build-release.ps1 -RequireClean

By default the script writes to the ignored outputs/release/ directory:

  • ys-helcim-via-fluentcart.zip
  • ys-helcim-via-fluentcart.manifest.json

The builder uses a strict runtime allowlist, a single ys-helcim-via-fluentcart/ root, normalized forward-slash entry names, one source-commit-derived deterministic ZIP timestamp, ordered entries, and sidecar SHA-256 hashes. The timestamp remains reproducible for one source commit without globally reusing the OPcache-unsafe 1980 value. It excludes tests, internal docs, scripts, manual probes, development dependencies, archives, logs, server paths, test card literals, and recognized secret formats.

Verify an existing artifact independently:

powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\verify-release-package.ps1 `
  -ZipPath .\outputs\release\ys-helcim-via-fluentcart.zip `
  -ManifestPath .\outputs\release\ys-helcim-via-fluentcart.manifest.json `
  -SourceRoot .

Deployment mtime and OPcache gate

The release ZIP deliberately excludes development scripts. A manual deployment runner must therefore take scripts/verify-deployed-php-mtime.php from the exact manifest source_commit, record the deployment epoch before extraction, touch the extracted runtime files, and run the gate before accepting hash parity or browser evidence:

deployment_epoch="$(date +%s)"
# Extract the already verified ZIP into the normal WordPress plugins directory.
find /path/to/wp-content/plugins/ys-helcim-via-fluentcart -type f -exec touch {} +
php /path/to/matching-release-tooling/verify-deployed-php-mtime.php \
  /path/to/wp-content/plugins/ys-helcim-via-fluentcart \
  "$deployment_epoch"

The gate requires at least one PHP runtime file, rejects every PHP file older than the deployment epoch, and fails closed on any file or directory symlink. The commit-derived ZIP timestamp also prevents routine WordPress/Hub extraction from reusing the same 1980 mtime across releases; manual touch plus this gate remains the stronger deployment evidence.

Run the executable package regression test:

powershell -NoProfile -ExecutionPolicy Bypass -File .\tests\package\ReleasePackage.Tests.ps1

Rebuild and verify the translation catalogs before the final package build:

php .\scripts\check-frontend-translations.php
php .\scripts\update-translations.php
powershell -NoProfile -ExecutionPolicy Bypass -File .\tests\package\FrontendTranslationContract.Tests.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\tests\package\Translations.Tests.ps1

The final GitHub release should attach exactly one verified ZIP asset. Record the release commit, ZIP SHA-256, Hub package version, and deployed manifest as separate anchors.

Release-candidate verification gates

Before replacing a client site's current payment gateway:

  • Confirm the exact WordPress, PHP, FluentCart, currency, database engine, Order Mode, and credential ownership.
  • Prove the current-mode hosted API token can call filtered GET /card-transactions and that the response is a root JSON list.
  • Back up the current plugin directory and relevant settings/operation rows.
  • Deploy the exact manifest-verified artifact that passed development testing.
  • Prove an approved inline purchase, a terminal decline that remains unpaid, duplicate-confirm replay safety, and lost-response recovery.
  • Prove full and partial remote-first refunds, open-batch reverse fallback, provider failure with no local refund, retry safety, and outbox recovery.
  • Prove valid, invalid, stale, and replayed webhooks.
  • Prove the hosted five-minute positive-only lookup, pre-70-minute empty/decline lock, seven-attempt pause, visible admin attention, and one-shot manual check without permitting a duplicate charge.
  • Confirm no raw PAN, CVV, reusable card token, API token, secret, or verifier appears in logs or plaintext persistence.
  • Prove both hosted and inline approved, declined, replay, lost-browser-response, webhook-recovery, and active-scope conflict paths against the same durable invariants.
  • Confirm no new PHP fatal/error and verify provider, operation, FluentCart transaction, and order state agree.

Security notes

  • Full card numbers and CVV must never reach WordPress logs, request serialization, order metadata, or operation rows.
  • Inline card inputs have no name attribute and are tokenized directly by Helcim.js.
  • Reusable inline card material may exist only in the encrypted short-lived operation envelope required for same-operation recovery, and is purged at terminal resolution/expiry.
  • Provider-changing requests require persistent idempotency keys and durable scope locks.
  • Webhook content is never trusted without signature verification and an API lookup.
  • The package verifier is a release hygiene control, not a substitute for credential rotation or a dedicated secret-scanning service.

Known limitations

  • Production deployment requires current credentials, recurring cron execution, and current evidence for every release gate above.
  • Only one-time purchase and refund/reverse operations are supported.
  • Subscriptions, pre-authorization/capture, and customer-facing saved cards are not supported.
  • Only USD and CAD are supported unless the gateway filter is deliberately extended and provider support is independently confirmed.
  • A settled refund gate depends on provider batch state; mocked or manually fabricated callbacks do not replace end-to-end evidence.

License

GPL v2 or later

Author

YANGSHEEP DESIGN — https://yangsheep.com.tw