Skip to content

Stripe Integration

Overview

Stripe is used for two distinct purposes in Upvendo:

  1. Online-ordering customer payments -- each payment profile gets its own Stripe connected account. Customer web/online-ordering payments are charged through that connected account (cards plus local methods such as Bancontact, depending on which capabilities Stripe has activated).
  2. Platform subscription billing -- Upvendo bills the merchant for platform services (kiosk devices, kitchen-display screens, online-ordering activations, loyalty) via Stripe subscriptions on Upvendo's own platform Stripe account, supporting card and SEPA Direct Debit.

These are separate Stripe relationships: customer payments flow to the merchant's connected account; subscription charges flow from the merchant to Upvendo's platform account. They are not the same account or the same money.

In-person / kiosk terminal payments are handled by Viva Wallet (or Square when connected), not Stripe. (A Stripe Terminal in-person flow exists in the codebase but is not enabled in the production back office -- see Business Rules.)

Key Purpose: Process online-ordering customer payments through per-profile Stripe connected accounts, and bill merchants for Upvendo platform subscriptions.

Note: Online-ordering payments run through Stripe (the profile's connected account); kiosk/in-person terminal payments run through Viva Wallet. If Square POS is connected, Square handles all customer payments and payment profiles are not used.

Purpose

Stripe is configured per payment profile. There is no standalone Stripe settings page -- all Stripe setup and management lives on the payment-profile detail page (/settings/payments/profiles/:id), in the "Stripe Connect" section. From there you onboard the connected account, view payout/balance information, edit the statement descriptor, business website, and payout schedule, and resolve any Stripe verification requirements.

Key Concepts

  • Stripe Connect (connected account): Each payment profile has its own Stripe connected account, created via Stripe's Account / Account Link API. Upvendo uses Connect with no Stripe dashboard access (controller.stripe_dashboard.type = none, requirement_collection = application), with the platform paying fees and absorbing losses. This is not an OAuth connect flow.
  • Destination charges: Online-ordering payments are created on Upvendo's platform account and transferred to the connected account using on_behalf_of + transfer_data[destination] (config services.stripe.use_destination_charges, default true). This keeps the platform fee opaque to the merchant.
  • Application fee (platform margin): When managed pricing is off, Upvendo adds an application_fee_amount (fixed via STRIPE_APPLICATION_FEE_FIXED, otherwise a percentage via STRIPE_APPLICATION_FEE_PERCENT, default 5%). When STRIPE_USE_MANAGED_PRICING is on, no fee is set in code and Stripe calculates it from the Dashboard pricing scheme.
  • Region keys: Stripe credentials are keyed by region in config -- services.stripe.eu and services.stripe.us (publishable/secret/webhook keys). The country code on the profile selects the region; any non-us country resolves to eu.
  • Required capabilities: card_payments and transfers are always requested; bancontact_payments is additionally requested for Belgium (BE) accounts. A connected account is considered ready for online-ordering payments only when charges_enabled, payouts_enabled, and the transfers capability are all active.
  • Subscription billing: A separate Stripe relationship on Upvendo's platform account. Per-device kiosk and kitchen-display subscriptions, plus online-ordering / loyalty subscriptions, are created against a Stripe Customer attached to the merchant's location.
  • SEPA Direct Debit: For subscription billing, SEPA Direct Debit is supported with mandate handling and an automatic retry path when a renewal payment fails.
  • Test mode: A per-location payment_test_mode flag (toggled on the profile detail page's Locations list) simulates payments for both Stripe and Viva -- no real charges. There is no Stripe live_mode field in the back office.

Actions

Set up the Stripe connected account

On the payment-profile detail page, click Set up Stripe (no Stripe account yet). You are first asked for a business website URL (required by Stripe before some EU methods like Bancontact can be enabled). Upvendo then creates a Stripe connected account and redirects you to Stripe's hosted account onboarding to provide identity, business, and bank details. After onboarding you return to the back office.

Resolve Stripe verification requirements

Verification status and outstanding requirements are surfaced in-app two ways:

  1. Stripe Connect embedded components (StripeNotificationBanner, account-onboarding, account-management widgets loaded with @stripe/connect-js).
  2. An Upvendo-built alert card above the balances section, shown whenever stripe.details_submitted is true and stripe.has_issues is true:
    • "Action required" (red, lock icon) when stripe.is_blocked is true (charges/payouts disabled or a disabled_reason is set).
    • "Verification upcoming" (orange/warning, clock icon) otherwise -- the account is active and processing, but Stripe will need more information by a deadline.
    • Below the title, it lists up to 3 humanized outstanding-requirement labels (falling through requirements.currently_due -> eventually_due -> future_currently_due -> future_eventually_due, first non-empty list wins), with a "+N more" line if there are more than 3, and a "Deadline: {date}" line when a deadline (current_deadline, falling back to future_deadline) is present.

Complete the requested items in the embedded panel; the alert card re-checks status automatically for a few attempts after you return from Stripe onboarding (auto-refresh is skipped when only future/eventually_due items remain, i.e. nothing urgent).

Edit Stripe account settings

On the profile detail page you can edit:

  • Statement descriptor (separate payments and payouts descriptors)
  • Business website URL
  • Payout schedule (interval daily/weekly/monthly, plus delay days and weekly/monthly anchors)

You can also view balances, payout history, and the payment-method status chips derived from the account's Stripe capabilities.

Switch a location to test mode

Test/live is controlled per location via the payment_test_mode switch on the profile detail page's Locations list (not via a Stripe-level toggle).

Location

  • Back-office route: Stripe is configured on the payment-profile detail page at /settings/payments/profiles/:id (there is no /settings/payments/stripe route).
  • Back-office view: src/views/settings/Payments/PaymentProfileDetail.vue (Stripe Connect section); connect flow via src/composables/useStripeConnect.ts and src/views/settings/Payments/ConnectStripeWebsiteDialog.vue; embedded components under src/components/stripe/ (e.g. StripeAccountOnboarding.vue, StripeNotificationBanner.vue, StripeAccountManagement.vue, StripeBalances.vue, StripePayouts.vue).
  • Backend controller: app/Http/Controllers/Api/PaymentProfileController.php (Stripe methods: createStripeUrl, createStripeAccountSession, updateStripeDescriptor, updateStripeBusinessWebsite, updateStripePayoutSchedule, showStripePayouts, stripePayoutsDataTable). There is no dedicated StripeController.
  • Backend service: app/Services/Payment/StripeService.php (Stripe API wrapper); onboarding/settings logic in app/Services/BackOffice/Settings/PaymentProfileService.php; model in app/RawModels/PaymentProfile.php.

Routes

Back-office API (per payment profile)

MethodRouteController methodPurpose
POST/back-office/settings/payment-profiles/{id}/stripe/urlcreateStripeUrlCreate connected account (if needed) + return hosted onboarding URL
POST/back-office/settings/payment-profiles/{id}/stripe/account-sessioncreateStripeAccountSessionCreate an AccountSession (client_secret) for embedded Connect components
PUT/back-office/settings/payment-profiles/{id}/stripe/descriptorupdateStripeDescriptorUpdate payments/payouts statement descriptors
PUT/back-office/settings/payment-profiles/{id}/stripe/business-websiteupdateStripeBusinessWebsiteUpdate the connected account's business profile URL
PUT/back-office/settings/payment-profiles/{id}/stripe/payout-scheduleupdateStripePayoutScheduleUpdate payout schedule
GET/back-office/settings/payment-profiles/{id}/stripe/payoutsshowStripePayoutsBalance + next/previous payout summary
GET/back-office/settings/payment-profiles/{id}/stripe/payouts/datatablestripePayoutsDataTablePaginated payout list

Webhook (public)

MethodRouteHandler
POST/api/stripe-webhook/{countryCode}WebhookController::stripeWebhook -> StripeWebhookOrchestrator

The route lives in routes/api/guest.phpguest there is middleware, not a URL prefix — so the public URL is just /api/stripe-webhook/{countryCode} under Laravel's /api prefix.

The webhook signature is verified against the per-region secrets services.stripe.{region}.webhook_secret (platform/subscription events) and services.stripe.{region}.webhook_secret_oo (online-ordering events); the orchestrator tries both.

Fields

Connected account onboarding

FieldField IDTypeRequiredNotes
Business websitebusiness_websiteURLYes (on first setup)Required before Stripe will enable some EU methods (e.g. Bancontact). Sent as the account business_profile.url.

Editable connected-account settings

FieldField IDTypeNotes
Payments statement descriptorpaymentsTextShown on the customer's bank statement; updated via Stripe account settings
Payouts statement descriptorpayoutsTextStatement descriptor for payouts
Business websiteurlURLConnected account business_profile.url
Payout intervalintervalSelectdaily / weekly / monthly
Payout delay daysdelay_daysNumberDays before funds are paid out
Weekly anchorweekly_anchorSelectDay of week (weekly interval)
Monthly anchormonthly_anchorNumberDay of month (monthly interval)

Read-only status (surfaced from the Stripe account)

FieldSourceNotes
Account IDstripe_account_idThe profile's connected account ID
Charges enabledcharges_enabledWhether the account can accept payments
Payouts enabledpayouts_enabledWhether the account can receive payouts
Transfers capabilitycapabilities.transfersMust be active for destination charges
Details submitteddetails_submittedWhether onboarding was completed
Outstanding requirements (due now)requirements.currently_dueItems Stripe needs immediately; non-empty is one of the triggers for "urgent" status
Outstanding requirements (due later)requirements.eventually_dueItems Stripe will need eventually; contributes to the "has upcoming items" state
Upcoming requirements (currently due)requirements.future_currently_dueFrom Stripe's future_requirements block -- items tied to a future deadline, not yet live in currently_due
Upcoming requirements (eventually due)requirements.future_eventually_dueFrom Stripe's future_requirements block -- longer-horizon upcoming items
Requirements deadlinerequirements.current_deadline (falls back to requirements.future_deadline)The deadline shown on the alert card ("Deadline: {date}")
Pending verificationrequirements.pending_verificationItems Stripe has received and is actively reviewing
Disabled reasonrequirements.disabled_reasonStripe's stated reason the account is restricted, if any
Payment-method capabilitiescapabilities.*E.g. card_payments, bancontact_payments, ideal_payments, sepa_debit_payments -- shown as read-only status chips

Computed status flags (derived server-side in ShowPaymentProfileResource, not raw Stripe fields):

  • has_issues -- true if there is anything to resolve: currently_due non-empty, disabled_reason set, charges_enabled/payouts_enabled false, a current_deadline set, or any eventually_due / future_currently_due / future_eventually_due items.
  • is_blocked -- true if charges_enabled or payouts_enabled is false, or disabled_reason is set. Drives the "Action required" state.
  • has_upcoming -- true if there are future/eventually_due items but nothing urgent (has_issues true and is_blocked false). Drives the "Verification upcoming" state.

Note: There are no per-method enable toggles (no card_enabled, ideal_enabled, bancontact_enabled, apple_pay_enabled, etc.) and no live_mode toggle in the back office. Which payment methods customers can use is determined by the connected account's Stripe capabilities, and test/live is the per-location payment_test_mode flag.

Business Logic

Online-ordering payment flow (Stripe connected account)

Customer completes an online order
        |
        v
PaymentService creates a Stripe PaymentIntent / Checkout Session
on the platform account, with on_behalf_of + transfer_data[destination]
pointing to the profile's connected account (and an application fee)
        |
        v
Customer pays (cards + capability-enabled local methods)
        |
        v
Stripe sends payment_intent.succeeded to /api/stripe-webhook/{countryCode}
        |
        v
StripeWebhookOrchestrator verifies and settles the order (OnlineOrderingOrchestrator)
        |
        v
Order confirmed

If a location is in payment_test_mode, the payment is simulated and no real charge is made. Free orders (total = 0) skip the provider entirely.

Platform subscription billing flow (Upvendo platform account)

Merchant adds a kiosk / KDS device or activates online ordering / loyalty
        |
        v
A Stripe subscription is created against the merchant's location-level
Stripe Customer on Upvendo's platform account (per-device for kiosk/KDS)
        |
        v
Stripe charges the merchant (card or SEPA Direct Debit)
        |
        v
Subscription active; renews on its billing cycle

For resellers on the "reseller collects" billing model, the Stripe subscription is created without collecting payment (it acts as a bookkeeping shell) and the reseller is settled via the commission/ledger system instead.

SEPA failure recovery

If a SEPA subscription renewal fails, StripeWebhookOrchestrator (on invoice.payment_failed, for SEPA subscription-cycle invoices) attempts a retry using the stored mandate. If the subscription is then deleted due to the failed payment, the orchestrator can recreate it with the original metadata and mandate data.


Business Rules

  • Stripe serves two separate roles: online-ordering customer payments via per-profile connected accounts, and platform subscription billing of the merchant on Upvendo's own platform account. Kiosk/in-person terminal payments are handled by Viva Wallet, or Square when connected.
  • Onboarding uses Stripe Connect with no dashboard access (Account + Account Link API), not OAuth. A business website URL is required before the connected account is created.
  • The connected account requests card_payments and transfers capabilities (plus bancontact_payments for Belgium). An account is ready for online-ordering payments only when charges_enabled, payouts_enabled, and the transfers capability are all active.
  • Stripe's future_requirements (items tied to a future deadline, not yet live in requirements.currently_due/eventually_due) are surfaced too, as future_currently_due / future_eventually_due / future_deadline on the payment-profile resource. The profile's alert card itemises requirements by falling through currently_due -> eventually_due -> future_currently_due -> future_eventually_due (first non-empty list wins), so upcoming Stripe deadlines are shown even before they become urgent.
  • Online-ordering payments use destination charges (on_behalf_of + transfer_data[destination]) created on the platform account, with a platform application fee (config-driven, or managed by Stripe pricing).
  • Stripe credentials are region-keyed (eu / us); any non-US country resolves to the eu region.
  • Webhooks hit /api/stripe-webhook/{countryCode} and are signature-verified against per-region webhook_secret (platform) and webhook_secret_oo (online ordering); events are processed by StripeWebhookOrchestrator (queued for async events).
  • Subscription billing supports card and SEPA Direct Debit; SEPA renewal failures trigger a retry and, if the subscription is deleted, a recreate-with-mandate path.
  • Test/live is controlled by the per-location payment_test_mode flag, not a Stripe-level live_mode toggle.
  • A Stripe Terminal in-person flow exists in the codebase (a terminal.reader.action_succeeded webhook handler keyed on a device's stripe_terminal_id), but reader registration and in-person Stripe payments are not enabled in the production back office -- in-person card readers are paired through Viva Wallet or Square. (Not fully verified beyond the production working tree; treat Stripe Terminal as not currently available to merchants.)

FAQs

  • What is Stripe used for in Upvendo? Two things: (1) processing online-ordering customer payments through your payment profile's Stripe connected account, and (2) billing you for Upvendo platform subscriptions (kiosk devices, KDS screens, online-ordering/loyalty activations) on Upvendo's own Stripe account. These are separate Stripe relationships.
  • When is Stripe used vs Viva Wallet? Stripe processes online-ordering customer payments; Viva Wallet handles kiosk/in-person terminal payments. Both are configured per payment profile. If Square is connected, Square handles all customer payments instead.
  • How do I connect Stripe? On the payment-profile detail page, click Set up Stripe, provide a business website URL when prompted, then complete Stripe's hosted onboarding. It is not an OAuth "Connect with Stripe" button -- Upvendo creates a Connect account for you and walks you through Stripe's onboarding and embedded verification widgets. There is no separate Stripe settings page.
  • Where do I enable iDEAL / Bancontact / Apple Pay? You don't toggle them in Upvendo. Available methods come from your connected account's Stripe capabilities (e.g. Bancontact is requested automatically for Belgian accounts). The back office shows each method as a read-only status chip; resolve any inactive ones through the embedded Stripe panel. Apple Pay / Google Pay are wallet representations of card payments and are not separate toggles. (Customer-facing storefront method availability is not verified here.)
  • What happens if my Stripe account is not verified? The profile's alert card shows "Action required" (charges or payouts disabled, or a blocking requirement) or "Verification upcoming" (the account is active, but Stripe needs more information -- current, future, or eventually-due items -- before a deadline). Your account is ready once Stripe has charges_enabled, payouts_enabled, and the transfers capability active. Complete any outstanding items listed on the alert card and in the embedded Stripe panel / notification banner on the profile detail page.
  • My account is active but Stripe says it needs more information soon -- what is that? That's the "Verification upcoming" state: your account can currently process payments (charges_enabled and payouts_enabled are true), but Stripe has requirements due by a future deadline (from eventually_due, or from Stripe's future_requirements -- future_currently_due / future_eventually_due). The profile's alert card lists the specific items and the deadline; nothing is disrupted yet, but complete them before the deadline to avoid a future block.
  • How do I test before going live? Turn on test mode for the location (payment_test_mode, on the profile detail page's Locations list). Payments are then simulated for both Stripe and Viva with no real charges. There is no Stripe live_mode toggle.
  • How do I link Upvendo to Apple Wallet? Apple Pay is a card-payment wallet accepted via Stripe when the connected account supports it. Apple Wallet passes are a separate, unrelated loyalty/member-card feature (configured under Settings -> Branding), not a way to link your merchant account to Apple Wallet.
  • Can I use Stripe Terminal for in-person payments? Not in the current back office -- in-person/kiosk card readers are paired through Viva Wallet (or Square). A Stripe Terminal flow exists in the codebase but is not enabled for merchants.
  • What is SEPA Direct Debit used for? It's a subscription billing payment method (charging the merchant for platform subscriptions). The system handles mandate data and automatically retries failed SEPA renewals, recreating the subscription with the mandate if it gets cancelled.

Troubleshooting

Problem: Online payments failing

Causes:

  1. Connected account onboarding incomplete (details_submitted false)
  2. Account restricted (charges_enabled / payouts_enabled false, or a disabled_reason)
  3. transfers capability not active (required for destination charges)
  4. Card declined on the customer's side
  5. Location is in test mode when a live charge was expected

Solutions:

  1. Complete onboarding on the profile detail page
  2. Resolve outstanding requirements in the embedded Stripe panel
  3. Ensure the transfers capability is active (Upvendo can re-request it)
  4. Ask the customer to try a different card
  5. Check the location's payment_test_mode setting

Problem: A local method (e.g. Bancontact) isn't available

Causes:

  1. The corresponding capability is not active on the connected account
  2. Business website not set (Stripe requires it before enabling some EU methods)

Solutions:

  1. Open the profile detail page and resolve the method's capability via the embedded Stripe panel
  2. Set the business website URL on the profile, then re-check the capability status

Problem: Stripe account not verified / pending / restricted

A connected account can need attention when Stripe has outstanding requirements. Once onboarding is submitted (details_submitted true) and there is anything outstanding (has_issues true), the profile's alert card shows one of two states:

  • Action required -- is_blocked is true: charges or payouts are disabled, or Stripe set a disabled_reason. Payments/payouts may be disrupted until resolved.
  • Verification upcoming -- has_upcoming is true: nothing is blocking yet, but there are eventually_due, future_currently_due, or future_eventually_due items Stripe will need before a deadline.

Either way the card lists up to 3 humanized requirement labels (currently-due items take priority, then eventually-due, then the future-requirements lists) plus the deadline, if any.

Causes:

  1. Onboarding never completed (details_submitted is false)
  2. Outstanding requirements (requirements.currently_due) -- e.g. ID verification, business details, a bank account
  3. Account restricted, so charges_enabled and/or payouts_enabled are false
  4. An approaching deadline for items Stripe will eventually need (eventually_due, or future_currently_due / future_eventually_due from Stripe's future_requirements)

Solutions:

  1. Open the profile detail page and complete the steps shown in the embedded Stripe panel / notification banner
  2. Provide every item Stripe lists as outstanding before the stated deadline
  3. The account is ready once charges_enabled, payouts_enabled, and the transfers capability are all active

Examples

Connected account onboarding payload (Belgium profile)

json
{
  "controller": {
    "stripe_dashboard": { "type": "none" },
    "fees": { "payer": "application" },
    "losses": { "payments": "application" },
    "requirement_collection": "application"
  },
  "capabilities": {
    "card_payments": { "requested": true },
    "transfers": { "requested": true },
    "bancontact_payments": { "requested": true }
  },
  "country": "be",
  "business_profile": { "url": "https://example.com" }
}

Account-ready check (online-ordering)

text
ready = charges_enabled AND payouts_enabled AND capabilities.transfers == "active"

Payout schedule update

json
{
  "interval": "weekly",
  "weekly_anchor": "monday",
  "delay_days": 3
}