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:
| Account | Legal entity | Currency | Used for |
|---|---|---|---|
| Stripe BR | MIDDAG Tecnologia LTDA | BRL | All Brazilian products |
| Stripe US/LLC | MIDDAG, LLC | USD | International 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:
| Variable | Description |
|---|---|
STRIPE_BR_SECRET_KEY | Secret key for Stripe BR |
STRIPE_BR_PUBLISHABLE_KEY | Publishable key for Stripe BR |
STRIPE_BR_WEBHOOK_SECRET | Webhook signing secret for BR |
STRIPE_LLC_SECRET_KEY | Secret key for Stripe US |
STRIPE_LLC_PUBLISHABLE_KEY | Publishable key for Stripe US |
STRIPE_LLC_WEBHOOK_SECRET | Webhook 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 account | Webhook URL |
|---|---|
| Stripe BR | https://yoursite.com/wp-json/middag-account/v1/webhooks/stripe/br |
| Stripe US/LLC | https://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:
- Explicit override -- An admin or the
X-Middag-CompanyAPI header specifies the account directly. - Product channels -- The product YAML defines which Stripe accounts it syncs with.
- Organization entity -- The organization's existing Stripe customer ID determines the account.
- Auto-detect -- Falls back to tax ID format (CNPJ = Brazil) or preferred currency.
Customer management
Each organization stores separate Stripe customer IDs:
| Field | Account |
|---|---|
stripe_customer_id_br | Stripe BR |
stripe_customer_id_global | Stripe 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 event | Plugin action |
|---|---|
invoice.paid | Invoice synced. Linked quote status set to paid. |
invoice.payment_failed | Payment recovery policy consulted. Entitlement may be suspended. |
subscription.updated | Entitlement updated (tier, period). |
subscription.deleted | Entitlement set to cancelled. |
charge.refunded | Invoice marked as refunded. Admin notified. |
Additional events
| Stripe event | Plugin action |
|---|---|
checkout.session.completed | Order created from Stripe Checkout session. |
payment_intent.succeeded | Order marked as paid. |
payment_intent.payment_failed | Payment failure recorded. |
customer.subscription.created | Local subscription record created or updated. |
charge.dispute.created | Admin 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 tier | Proration behavior | Notes |
|---|---|---|
| Plugins | create_prorations | Standard Stripe proration on changes |
| Support | none | One-time charges, no proration needed |
| Platform | create_prorations | Add-ons prorated mid-cycle |
| Services | always_invoice | Changes 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):
- Add environment variables for the new Stripe account.
- Register the account in the DI container.
- Add the entity to the account resolver whitelist.
- Register a new webhook endpoint (
/webhooks/stripe/eu). - Add a
stripe_customer_id_eufield to organizations. - Configure the webhook in the Stripe Dashboard.
No changes to the core webhook handlers or API filters are needed.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Webhooks returning 401 | Wrong webhook signing secret | Verify the secret matches the Stripe Dashboard |
| Payments not syncing | Webhook URL not registered in Stripe | Add the endpoint URL in Stripe > Webhooks |
| Wrong account used for payment | Missing product channels block | Verify the product YAML has Stripe channels |
| Duplicate invoice records | Idempotency check failing | Check stripe_event_id tracking in the database |
| Entitlement not updating | Subscription event not in the handled list | Verify the event type is enabled in Stripe |