Skip to content

Stripe Connect Integration

Miranda uses Stripe Connect in a platform model with Express connected accounts. Each foundation connects their own Stripe account, and Miranda facilitates the payment flow while collecting a tiered platform fee.

Miranda operates as the platform account (merchant of record). Each foundation is an Express connected account. Donations use destination charges, meaning:

  • The PaymentIntent is created on Miranda’s platform account
  • Funds are automatically transferred to the foundation’s connected account
  • Miranda retains the application_fee_amount as its transaction fee
Donor → Stripe.js → Miranda Platform Account
PaymentIntent (destination charge)
Foundation's Connected Account (receives funds minus fee)
Miranda retains application_fee_amount

The StripeConnectService manages the connected account lifecycle.

public function createExpressAccount(Foundation $foundation): string;

Creates a Stripe Express account with the foundation’s ID in metadata. Updates the foundation record with stripe_connect_id and sets stripe_connect_status to pending.

public function generateOnboardingLink(
Foundation $foundation,
string $returnUrl,
string $refreshUrl,
): string;

Returns a Stripe Account Link URL that redirects the foundation owner through Stripe’s hosted onboarding flow. After completion, Stripe redirects back to returnUrl.

public function getAccountStatus(Foundation $foundation): array;

Returns:

{
"account_id": "acct_abc123",
"charges_enabled": true,
"payouts_enabled": true,
"details_submitted": true
}

A foundation can accept donations once charges_enabled is true.

public function getBusinessProfile(Foundation $foundation): array;

Returns:

{
"business_name": "Miranda Faith Memorial Foundation",
"support_email": "[email protected]",
"support_phone": "+15551234567",
"business_type": "non_profit"
}
public function deleteAccount(Foundation $foundation): void;

Deletes the connected account from Stripe. Used when a foundation disconnects their Stripe integration.

public function calculatePlatformFee(int $amountInCents, Foundation $foundation): int;

Fee rates are tiered by plan (from config/plans.php):

PlanFee rateExample ($100 donation)
Starter2.0%$2.00
Growth1.5%$1.50
Pro1.0%$1.00

The fee is calculated in cents and rounded to the nearest cent:

$plan = $foundation->settings['plan'] ?? 'starter';
$feeRate = config("plans.{$plan}.transaction_fee");
return (int) round($amountInCents * $feeRate);

The full onboarding sequence from the admin dashboard:

  1. Initiate: Admin clicks “Connect Stripe” in settings
  2. API call: POST /api/stripe/onboard creates an Express account and returns an onboarding URL
  3. Redirect: Admin is redirected to Stripe’s hosted onboarding
  4. Complete: After providing business details and bank account, Stripe redirects back
  5. Callback: GET /api/stripe/callback verifies the account status
  6. Ready: Once charges_enabled is true, the foundation can accept donations
MethodPathDescription
POST/api/stripe/onboardCreate Express account and return onboarding URL
GET/api/stripe/callbackHandle return from Stripe onboarding
GET/api/stripe/statusCheck connected account status
POST/api/stripe/disconnectDisconnect Stripe account
GET/api/stripe/account-detailsRetrieve business profile from Stripe

All endpoints require foundation authentication.

Stripe webhooks are received at:

  • POST /stripe/webhook (primary)
  • POST /strp/wbhk (alias for Stripe dashboard configuration)

The WebhookController validates the webhook signature and processes events for payment status updates, failed charges, and subscription lifecycle events.

FieldTypeDescription
stripe_connect_idstring?Stripe connected account ID (e.g., acct_abc123)
stripe_connect_statusstringpending, active, or disconnected
  • PCI compliance: Miranda operates at SAQ-A level. Card data is collected client-side via Stripe.js/Elements and never touches Miranda’s servers.
  • Webhook verification: All Stripe webhooks are validated using the webhook signing secret.
  • Platform keys: Stripe API keys are stored in .env and accessed via config('services.stripe.secret').