Stripe Checkout & Payments

ClientCove uses Stripe for invoice payments — clients enter their card details on a portal-hosted page (no redirect to Stripe), the charge processes through Stripe's API, and a webhook confirms the result back to ClientCove. This page covers the complete setup and flow.

How Payments Work

The payment flow at a glance:

  1. Client opens the invoice in the portal
  2. Clicks Pay Invoice — a side drawer opens
  3. Enters email and card details
  4. ClientCove calls Stripe to create a Payment Intent for the invoice amount
  5. Stripe collects the card via Stripe.js (PCI-compliant — card never touches your server)
  6. Stripe processes the charge
  7. Stripe sends a webhook to ClientCove confirming success/failure
  8. ClientCove updates invoice status to Paid
  9. Sends payment confirmation email
  10. Client sees a success state in the drawer

This is Stripe Payment Intents flow, the modern alternative to the older Charges API. It supports SCA (Strong Customer Authentication / 3D Secure), which is required in Europe and recommended everywhere.

Configuring Stripe

Stripe is configured in Settings → Stripe (admin only). The settings page collects:

SettingPurpose
Publishable KeyThe pk_live_... (or pk_test_...) key used by client-side JavaScript to tokenize cards
Secret KeyThe sk_live_... (or sk_test_...) key used server-side to create Payment Intents and verify webhooks
Webhook SecretThe whsec_... signing secret for verifying that incoming webhooks are genuinely from Stripe
Webhook URL(read-only) The URL Stripe should POST events to — copy this into your Stripe Dashboard

All keys are stored securely in WordPress options. The secret key never appears in any client-side code or HTML.

API Keys

To get your API keys:

  1. Sign in to the Stripe Dashboard
  2. Navigate to Developers → API Keys
  3. You'll see two keys:
    • Publishable key — starts with pk_live_ (or pk_test_ in test mode)
    • Secret key — starts with sk_live_ (or sk_test_ in test mode)
  4. Copy both keys
  5. In ClientCove, paste them into Settings → Stripe:
    • Publishable Key field
    • Secret Key field (stored encrypted, masked in the UI after save)
  6. Click Save Settings

ClientCove validates the keys by making a test API call. A green "Connected" indicator appears next to the secret key field on success.

Test Mode vs Live Mode

Stripe supports both test mode (no real money moves) and live mode (real charges). The mode is determined by the key prefix:

ModePublishableSecretWebhook URL
Testpk_test_...sk_test_...Same URL, but Stripe routes test events
Livepk_live_...sk_live_...Same URL, Stripe routes live events

ClientCove auto-detects the mode based on the secret key prefix and shows a TEST MODE badge in the dashboard when test keys are configured.

Recommended setup

  1. Start in test mode with test keys to verify the integration
  2. Run several test transactions (use Stripe's test cards like 4242 4242 4242 4242)
  3. Verify webhooks fire correctly
  4. Switch to live keys when ready for production
  5. Don't forget to register a separate webhook endpoint for live mode in your Stripe Dashboard

Webhook Endpoint

Webhooks are how Stripe tells ClientCove that a payment succeeded, failed, or was refunded. Setup:

1. Get the webhook URL

In Settings → Stripe, the Webhook URL field shows your unique endpoint (read-only). Click the copy button.

The URL is typically:

https://yourportal.com/wp-json/lcmPortal/v1/stripe/webhook

2. Register the webhook in Stripe

  1. Sign in to Stripe Dashboard
  2. Go to Developers → Webhooks
  3. Click Add endpoint
  4. Paste your webhook URL
  5. Select events to listen for:
    • payment_intent.succeeded
    • payment_intent.payment_failed
    • charge.refunded
    • charge.dispute.created
  6. Click Add endpoint

3. Copy the signing secret

After creating the endpoint, Stripe shows the Signing secret (starts with whsec_...). Copy it.

4. Paste into ClientCove

Back in ClientCove Settings → Stripe, paste the signing secret into the Webhook Secret field. Save.

Why the webhook secret matters

Stripe signs every webhook with your secret. ClientCove verifies the signature on each incoming webhook to ensure it actually came from Stripe — without this, anyone could POST fake "payment succeeded" events to your endpoint and mark invoices Paid without paying. The signing secret protects against this.

The Client Payment Flow

When a client opens an unpaid invoice:

1. The invoice page

  • Shows the invoice details, totals, status badge
  • A green Pay Invoice button (visible only if balance is due and Stripe is configured)
  • The button label includes the amount: "Pay $1,827.50"

2. Click Pay Invoice

  • A side drawer slides in from the right
  • Header: "Pay Invoice" with credit card icon
  • Top section: invoice number, client name, amount due (large, prominent)
  • Body: email field (pre-filled with the client's account email) + card form

3. Enter card details

  • Stripe Elements renders the card input (handled by Stripe.js, not ClientCove)
  • Real-time validation: card number format, expiry, CVC
  • Detects card brand (Visa, Mastercard, Amex) and shows the icon

4. Submit payment

  • Click the green Pay button (also shows the amount)
  • Button shows a spinner while processing
  • ClientCove creates a Payment Intent server-side
  • Stripe.js confirms the payment with the card details
  • For SCA-required cards, the 3D Secure modal opens automatically
  • Result is returned to the page

5. Result

  • Success: Green confirmation in the drawer — "Payment successful! Invoice marked as paid."
  • Failure: Red error message with the specific reason ("Your card was declined", "Insufficient funds", etc.)
  • Either way, the audit log records the attempt with timestamp + IP

6. Confirmation

On success:

  • The drawer closes after a few seconds
  • The invoice page reloads showing Paid status
  • The billing contact gets a payment confirmation email
  • The Stripe webhook fires (typically within seconds), confirming server-side

The Payment Drawer

The payment drawer is an off-canvas slide-in (Bootstrap offcanvas) on the right side of the invoice page, 500px wide.

Why a drawer not a modal?

  • Less disruptive — the invoice stays visible behind the drawer
  • Better for context — the client can see what they're paying for while paying
  • Works on mobile — collapses to full-width drawer on small screens

Drawer state

  • Closed by default — no payment UI shown until the client clicks Pay
  • Opens via Bootstrap's offcanvas API
  • Can be closed via X button or backdrop click (cancels the payment)
  • Auto-closes on successful payment

Card form

The drawer uses Stripe Elements for the card input — a Stripe-hosted iframe that:

  • Tokenizes the card before submission
  • Never sends the card number to your server (PCI compliance)
  • Handles all card validation and formatting
  • Supports Apple Pay and Google Pay if your Stripe account is set up for them

Payment Intents

ClientCove uses Stripe's Payment Intents API (not the older Charges API). The flow:

  1. Client clicks Pay
  2. ClientCove POSTs to /wp-json/clientcove/v1/invoices/<id>/payment-intent
  3. The endpoint:
    • Loads the invoice
    • Verifies the invoice is unpaid and the user has access
    • Creates a Stripe Payment Intent for the balance due
    • Returns the Payment Intent's client_secret to the browser
  4. Browser uses the client_secret to confirm the payment with Stripe.js
  5. Stripe processes the card
  6. ClientCove receives the success/failure via webhook
  7. ClientCove POSTs to /wp-json/clientcove/v1/invoices/<id>/confirm-payment to finalize state

The Payment Intent supports:

  • SCA / 3D Secure — required in Europe, recommended elsewhere; auto-handled by Stripe.js
  • Idempotency — re-submitting the same payment doesn't create duplicate charges
  • Multi-currency — the Payment Intent uses the invoice's currency

Webhook Events

ClientCove listens for these Stripe events:

payment_intent.succeeded

Sent when a payment completes successfully. ClientCove:

  • Marks the invoice Paid
  • Records the payment in the invoice's payment history (amount, date, Stripe charge ID)
  • Sends the payment confirmation email
  • Logs the event

payment_intent.payment_failed

Sent when a payment fails (declined card, insufficient funds, etc.). ClientCove:

  • Logs the failure with the specific reason
  • Does NOT change invoice status (it stays Sent / Viewed)
  • Optionally sends a failure notification (configurable)

charge.refunded

Sent when an admin issues a refund through the Stripe Dashboard. ClientCove:

  • Records the refund amount
  • If full refund: status flips back to Sent (or stays Paid with refund noted, depending on config)
  • Logs the refund

charge.dispute.created

Sent when a customer disputes a charge with their bank. ClientCove:

  • Logs the dispute
  • Optionally notifies admin (you'll want to handle disputes in the Stripe Dashboard manually)

All webhook events are signature-verified before processing. Invalid signatures are rejected with a 400 response.

Manual Mark as Paid

Sometimes a client pays outside of Stripe (check, ACH, wire, in-person cash). For these, manually mark the invoice paid:

  1. Open the invoice (admin/editor)
  2. Click Mark as Paid in the toolbar
  3. A modal opens asking for:
    • Payment amount (defaults to balance due)
    • Payment date (defaults to today)
    • Payment method (Check / ACH / Wire / Cash / Other)
    • Reference (check number, transaction ID, etc.)
  4. Save

ClientCove:

  • Records the payment in the invoice's payment history
  • If the payment covers the balance: marks status Paid
  • If partial: leaves status as is, reduces balance due
  • Sends the payment confirmation email
  • Logs the action

Manual payments mix with Stripe payments — an invoice can have both, and ClientCove tracks each separately in the payment history.

Failed & Disputed Payments

Failed payments

Failed Stripe payments don't change invoice status — the invoice stays Sent or Viewed and the client can try again. The failure is logged and (optionally) emailed to admin so you can follow up.

Common failure reasons:

  • Card declined
  • Insufficient funds
  • Card expired
  • 3D Secure authentication failed
  • Card requires authentication that the client didn't complete

For each, the error message in the drawer tells the client what to do next.

Disputes

If a client disputes a charge with their bank (e.g. claims they didn't authorize it):

  1. Stripe sends a charge.dispute.created webhook
  2. ClientCove logs the dispute on the invoice
  3. The invoice status doesn't auto-change (you'll handle the dispute manually in Stripe)
  4. Use the Stripe Dashboard's dispute interface to provide evidence and respond

Disputes are time-sensitive — Stripe typically gives you a few days to respond. Set up dispute notifications in Stripe Dashboard → Notifications so you don't miss them.

Refunds

Refunds are issued through the Stripe Dashboard, not ClientCove:

  1. Sign in to Stripe Dashboard
  2. Find the payment
  3. Click Refund
  4. Enter amount (full or partial) and reason
  5. Confirm

Stripe processes the refund and sends ClientCove a charge.refunded webhook. ClientCove updates the invoice's payment history.

For partial refunds, the invoice may stay in Paid status with the refund noted; for full refunds, the invoice may flip back to Sent (configurable in your portal settings).

The refund typically takes 5–10 business days to appear on the customer's card statement, depending on their bank.

Was this page helpful?