Skip to content

Stripe Integration

MIDDAG Account integrates with Stripe for payment processing, subscription management, and invoice handling. The integration runs in dual-account mode to support two legal entities billing in different currencies.

Dual-account architecture

MIDDAG operates two Stripe accounts:

AccountLegal entityCurrencyUsed for
Stripe BRMIDDAG Tecnologia LTDABRLAll Brazilian products
Stripe US/LLCMIDDAG, LLCUSDInternational plugins and services

Each account has its own API keys, webhook endpoint, and signing secret. The plugin routes transactions to the correct account automatically.

Setup

API keys

Configure the following environment variables in your wp-config.php or environment:

VariableDescription
STRIPE_BR_SECRET_KEYSecret key for Stripe BR
STRIPE_BR_PUBLISHABLE_KEYPublishable key for Stripe BR
STRIPE_BR_WEBHOOK_SECRETWebhook signing secret for BR
STRIPE_LLC_SECRET_KEYSecret key for Stripe US
STRIPE_LLC_PUBLISHABLE_KEYPublishable key for Stripe US
STRIPE_LLC_WEBHOOK_SECRETWebhook signing secret for US

You can find these values in the Stripe Dashboard under Developers > API keys and Webhooks.

Webhook endpoints

Register these URLs in each Stripe Dashboard:

Stripe accountWebhook URL
Stripe BRhttps://yoursite.com/wp-json/middag-account/v1/webhooks/stripe/br
Stripe US/LLChttps://yoursite.com/wp-json/middag-account/v1/webhooks/stripe/llc

Account resolution

When a payment or subscription event occurs, the plugin determines which Stripe account to use through a cascading resolution:

  1. Explicit override -- An admin or the X-Middag-Company API header specifies the account directly.
  2. Product channels -- The product YAML defines which Stripe accounts it syncs with.
  3. Organization entity -- The organization's existing Stripe customer ID determines the account.
  4. Auto-detect -- Falls back to tax ID format (CNPJ = Brazil) or preferred currency.

Customer management

Each organization stores separate Stripe customer IDs:

FieldAccount
stripe_customer_id_brStripe BR
stripe_customer_id_globalStripe US

Customers are created lazily -- only when the first transaction is routed to that account. An organization can have customer records in both accounts (rare, but supported).

Synced data

Payments and invoices

WooCommerce is the source of truth for billing state. Stripe feeds WooCommerce through webhooks, and the plugin reads from WooCommerce. The plugin never reads billing state directly from Stripe.

Subscriptions

Subscription changes in Stripe (upgrades, downgrades, cancellations) are reflected in the plugin's entitlement records. Tier, period, and status are updated when Stripe sends subscription events.

Webhook events

Primary events

Stripe eventPlugin action
invoice.paidInvoice synced. Linked quote status set to paid.
invoice.payment_failedPayment recovery policy consulted. Entitlement may be suspended.
subscription.updatedEntitlement updated (tier, period).
subscription.deletedEntitlement set to cancelled.
charge.refundedInvoice marked as refunded. Admin notified.

Additional events

Stripe eventPlugin action
checkout.session.completedOrder created from Stripe Checkout session.
payment_intent.succeededOrder marked as paid.
payment_intent.payment_failedPayment failure recorded.
customer.subscription.createdLocal subscription record created or updated.
charge.dispute.createdAdmin notified, dispute recorded.

Signature validation

Every incoming webhook is validated using HMAC-SHA256 with the Stripe-Signature header. The tolerance window is 300 seconds (5 minutes). Webhooks that fail signature validation are rejected with HTTP 401 and logged as errors.

Each Stripe account uses its own webhook signing secret. Never share secrets between accounts.

Proration behavior

Product tierProration behaviorNotes
Pluginscreate_prorationsStandard Stripe proration on changes
SupportnoneOne-time charges, no proration needed
Platformcreate_prorationsAdd-ons prorated mid-cycle
Servicesalways_invoiceChanges generate an immediate invoice

Transaction tagging

Every financial record (order, invoice, license, contract) carries a billing_entity tag (middag_br or middag_global). The REST API supports filtering by entity: ?billing_entity=middag_br.

Adding a new entity

The dual-account architecture is designed to scale. To add a third entity (for example, middag_eu):

  1. Add environment variables for the new Stripe account.
  2. Register the account in the DI container.
  3. Add the entity to the account resolver whitelist.
  4. Register a new webhook endpoint (/webhooks/stripe/eu).
  5. Add a stripe_customer_id_eu field to organizations.
  6. Configure the webhook in the Stripe Dashboard.

No changes to the core webhook handlers or API filters are needed.

Troubleshooting

SymptomLikely causeFix
Webhooks returning 401Wrong webhook signing secretVerify the secret matches the Stripe Dashboard
Payments not syncingWebhook URL not registered in StripeAdd the endpoint URL in Stripe > Webhooks
Wrong account used for paymentMissing product channels blockVerify the product YAML has Stripe channels
Duplicate invoice recordsIdempotency check failingCheck stripe_event_id tracking in the database
Entitlement not updatingSubscription event not in the handled listVerify the event type is enabled in Stripe