WP Manifestindependent plugin directory
manifest / integrations / citta-stripe-iinsight

CITTA Stripe to iinsight Bridge

Receives Stripe payment webhooks, verifies them, logs each event, emails the team, and exposes a clearly-marked placeholder for the future iinsight case-creation API call (Phase 2).

by Stallioni Net Solutions · github.com/sanjeev-stallioni/citta-stripe-iinsight · 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/sanjeev-stallioni/citta-stripe-iinsight/archive/refs/heads/main.zip

A lightweight, single-file WordPress plugin that receives Stripe payment webhooks, verifies them, logs each event, emails the CITTA team, and (Phase 2) automatically creates the client "case" in iinsight.

Built for CITTA, a trauma-informed NDIS recovery service. The code favours calm, predictable, auditable behaviour over cleverness.


What it does (Phase 1 — live)

  1. Receives Stripe webhooks at a dedicated REST endpoint.
  2. Verifies the Stripe signature (HMAC-SHA256) before trusting any data.
  3. Processes only checkout.session.completed (paid) events; safely ignores the rest. This event carries the Payment Link's program metadata plus the customer details, so program detection works without code changes.
  4. Extracts customer name, email, phone, amount, currency and program.
  5. Logs each event to a custom database table (with idempotency so Stripe's duplicate deliveries never double-process).
  6. Emails an admin notification to the configured address.
  7. Creates the iinsight case (Phase 2, when enabled) and fires a citta_stripe_payment_processed action for any further extension.

Phase 2 — automatic iinsight case creation (implemented)

When enabled, a successful payment also creates a client (Person) case in iinsight:

  1. Authenticates (POST /auth/login, Basic Auth + form-data) and caches the bearer token in a transient until it expires.
  2. POST /api1/cases/new_client with the customer data and the configured IDs.
  3. Stores the returned case_number in iinsight_case_id and sets iinsight_status to created (or failed) — visible in the Event Log.
  4. Emails the team if creation fails, so the case can be created manually.

Case creation only runs after the event row is logged, so a Stripe retry can never create a duplicate case.

Configuring Phase 2 (Settings → CITTA Stripe → iinsight Integration)

Setting Notes
Enable integration Off by default
Environment UAT (sandbox) or Production — separate URLs
Client ID / Client Secret Basic Auth pair (generated on the iinsight platform)
iinsight Username / Password Service-account user login (form-data)
Business Division ID from GET /api1/cases/business_divisions
Service Contract ID from GET /api1/cases/service_contracts
Finance Template ID from GET /api1/cases/finance_templates
Team ID / Assigned-to User ID from the iinsight Users endpoints

Secrets are never displayed back or logged; leaving a secret field blank keeps the stored value. Test in UAT before switching to Production.


Installation

  1. Copy the citta-stripe-iinsight/ folder into wp-content/plugins/.
  2. In WordPress admin → Plugins, activate CITTA Stripe to iinsight Bridge. Activation creates the {prefix}citta_stripe_events table.
  3. Go to Settings → CITTA Stripe.
  4. Copy the webhook endpoint URL shown at the top of the page, e.g. https://your-site.com/wp-json/citta/v1/stripe-webhook.

Connecting Stripe

  1. In the Stripe Dashboard, add a new webhook endpoint and paste the URL above.
  2. Subscribe it to the checkout.session.completed event.
  3. Copy the endpoint's Signing secret (starts with whsec_).
  4. Paste it into Settings → CITTA Stripe → Stripe Signing Secret and save.

The stored secret is never displayed back or written to any log. Leave the field blank when saving to keep the existing secret.

Test vs Live mode: Stripe keeps separate webhook endpoints and separate signing secrets for sandbox/test and live. Pay in the same mode as the endpoint you created, and paste that mode's signing secret. A live-mode secret on a test payment (or vice versa) fails signature verification (HTTP 401) and nothing is logged.

Reachability: Stripe must be able to reach the endpoint over the public internet — a localhost/.test URL won't receive live webhooks. For local testing use the Stripe CLI: stripe listen --forward-to <webhook-url>.

Each Payment Link needs a little configuration so the plugin can identify the program and collect the customer details iinsight requires.

1. Program metadata (required)

Set a Metadata key named program on the Payment Link (Dashboard → the link → Metadata) with one of:

program value Displayed as
foundation Citta Foundation Program
integration_circle Citta Integration Circle

This works because the plugin listens to checkout.session.completed, and Stripe copies the Payment Link's metadata onto the Checkout Session. (It does not appear on the PaymentIntent — which is why the plugin uses the Checkout Session event, not payment_intent.succeeded.) Unknown or missing values are still logged; they simply show the raw key.

Per-link: metadata lives on each individual Payment Link. A new link (e.g. after a price change) needs its own program value.

2. Customer fields to collect (Options section)

Option Setting Why
Collect customer names On, not optional iinsight needs first + last name
Require phone number On (recommended) maps to the iinsight case mobile
Collect business names Off avoids the business name overwriting the person's name
Collect customer addresses Optional not used by the plugin

Stripe collects the name as a single field; the plugin splits it (first word = first name, remainder = last name) and always keeps the full name. Email is collected automatically.

Prices

Program prices are managed entirely in Stripe — the plugin reads the paid amount dynamically from each Checkout Session, so you can change a price (or spin up a new link) anytime without touching the plugin. The amount shown in the log and email always reflects what the customer actually paid.

Customer receipts / invoices

This plugin emails your team, not the customer. If you want the customer to receive a receipt, enable it under Stripe Settings → Customer emails (note: Stripe only sends these in live mode, not sandbox). For a formal tax invoice, turn on Post-payment invoice on the Payment Link. Both are Stripe features, independent of this plugin.


Settings reference

Setting Default Notes
Stripe Signing Secret (empty) whsec_...; required for webhooks to be accepted.
Notification Email (set in settings) Where payment notifications are sent.
Logging On Records events; also powers idempotency.
Admin Emails On Sends the notification email per payment.
Email Subject (built-in default) Editable subject line; supports merge tags. Blank = default.
Email Body (built-in default) Editable HTML body; supports merge tags. Blank = default.
Delete Data on Uninstall Off When off, history & settings survive plugin deletion.

Editing the notification email

The recipient, subject, and HTML body are all editable under Settings → CITTA Stripe — nothing about the email is hardcoded. Use these merge tags in the subject or body and they're replaced per payment:

Tag Replaced with
{program} Program name (e.g. Citta Foundation Program)
{amount} Amount with currency (e.g. AUD $2,327.88)
{name} Customer full name
{first_name} / {last_name} Customer first / last name
{email} Customer email
{phone} Customer phone
{currency} Currency code (e.g. AUD)
{payment_intent} Stripe payment intent ID
{timestamp} Date/time the payment was received

Leave either field blank and save to restore the built-in default. Customer values are HTML-escaped when merged, so they can't break the layout; the body is sanitised with wp_kses_post() on save (scripts/iframes stripped, email-safe table/inline-style markup kept).

Event log

Settings → CITTA Stripe Log lists the 50 most recent events (newest first): date, payment intent, customer, email, program, amount and iinsight status. Each row has a View raw payload section for debugging.


Lifecycle behaviour

  • Activation — creates the table (via dbDelta) and seeds default options.
  • Deactivation — does not drop the table or delete settings; logs and config are preserved.
  • Uninstall (delete) — drops the table and removes options only if "Delete Data on Uninstall" was enabled. Otherwise nothing is destroyed.

Security notes

  • Webhook signature is verified before any other processing.
  • Signatures compared with hash_equals() (constant-time); timestamps outside a 5-minute window are rejected as possible replays.
  • All DB writes use $wpdb prepared statements / format arrays.
  • All admin output is escaped; the settings form uses the WordPress Settings API nonce.
  • The signing secret and full card data are never logged.

HTTP responses returned to Stripe

Code Meaning
200 Handled, ignored, or duplicate — Stripe stops retrying.
400 Malformed request (empty body / invalid JSON).
401 Missing or invalid signature.
500 Not configured or unexpected server error — Stripe will retry.

Requirements

  • WordPress 5.8+
  • PHP 7.4+

Extending

The iinsight integration (Phase 2) is built in. For anything further — a CRM sync, a Slack ping, a second email — hook the action that fires after every processed payment:

add_action( 'citta_stripe_payment_processed', function ( $data ) {
    // $data contains the sanitised payment + customer fields
    // (program_label, amount, customer_name, customer_email, etc.).
} );

Version 1.1.0 — Phase 1 (webhook, logging, email) and Phase 2 (automatic iinsight case creation) both implemented. Configure Phase 2 under Settings → CITTA Stripe and test in the UAT sandbox before going live.