Appearance
Stripe Integration
Overview
Stripe is used for two distinct purposes in Upvendo:
- 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).
- 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](configservices.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 viaSTRIPE_APPLICATION_FEE_FIXED, otherwise a percentage viaSTRIPE_APPLICATION_FEE_PERCENT, default 5%). WhenSTRIPE_USE_MANAGED_PRICINGis 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.euandservices.stripe.us(publishable/secret/webhook keys). The country code on the profile selects the region; any non-uscountry resolves toeu. - Required capabilities:
card_paymentsandtransfersare always requested;bancontact_paymentsis additionally requested for Belgium (BE) accounts. A connected account is considered ready for online-ordering payments only whencharges_enabled,payouts_enabled, and thetransferscapability 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_modeflag (toggled on the profile detail page's Locations list) simulates payments for both Stripe and Viva -- no real charges. There is no Stripelive_modefield 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:
- Stripe Connect embedded components (
StripeNotificationBanner, account-onboarding, account-management widgets loaded with@stripe/connect-js). - An Upvendo-built alert card above the balances section, shown whenever
stripe.details_submittedis true andstripe.has_issuesis true:- "Action required" (red, lock icon) when
stripe.is_blockedis true (charges/payouts disabled or adisabled_reasonis 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 tofuture_deadline) is present.
- "Action required" (red, lock icon) when
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/striperoute). - Back-office view:
src/views/settings/Payments/PaymentProfileDetail.vue(Stripe Connect section); connect flow viasrc/composables/useStripeConnect.tsandsrc/views/settings/Payments/ConnectStripeWebsiteDialog.vue; embedded components undersrc/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 dedicatedStripeController. - Backend service:
app/Services/Payment/StripeService.php(Stripe API wrapper); onboarding/settings logic inapp/Services/BackOffice/Settings/PaymentProfileService.php; model inapp/RawModels/PaymentProfile.php.
Routes
Back-office API (per payment profile)
| Method | Route | Controller method | Purpose |
|---|---|---|---|
| POST | /back-office/settings/payment-profiles/{id}/stripe/url | createStripeUrl | Create connected account (if needed) + return hosted onboarding URL |
| POST | /back-office/settings/payment-profiles/{id}/stripe/account-session | createStripeAccountSession | Create an AccountSession (client_secret) for embedded Connect components |
| PUT | /back-office/settings/payment-profiles/{id}/stripe/descriptor | updateStripeDescriptor | Update payments/payouts statement descriptors |
| PUT | /back-office/settings/payment-profiles/{id}/stripe/business-website | updateStripeBusinessWebsite | Update the connected account's business profile URL |
| PUT | /back-office/settings/payment-profiles/{id}/stripe/payout-schedule | updateStripePayoutSchedule | Update payout schedule |
| GET | /back-office/settings/payment-profiles/{id}/stripe/payouts | showStripePayouts | Balance + next/previous payout summary |
| GET | /back-office/settings/payment-profiles/{id}/stripe/payouts/datatable | stripePayoutsDataTable | Paginated payout list |
Webhook (public)
| Method | Route | Handler |
|---|---|---|
| POST | /api/stripe-webhook/{countryCode} | WebhookController::stripeWebhook -> StripeWebhookOrchestrator |
The route lives in routes/api/guest.php — guest 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
| Field | Field ID | Type | Required | Notes |
|---|---|---|---|---|
| Business website | business_website | URL | Yes (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
| Field | Field ID | Type | Notes |
|---|---|---|---|
| Payments statement descriptor | payments | Text | Shown on the customer's bank statement; updated via Stripe account settings |
| Payouts statement descriptor | payouts | Text | Statement descriptor for payouts |
| Business website | url | URL | Connected account business_profile.url |
| Payout interval | interval | Select | daily / weekly / monthly |
| Payout delay days | delay_days | Number | Days before funds are paid out |
| Weekly anchor | weekly_anchor | Select | Day of week (weekly interval) |
| Monthly anchor | monthly_anchor | Number | Day of month (monthly interval) |
Read-only status (surfaced from the Stripe account)
| Field | Source | Notes |
|---|---|---|
| Account ID | stripe_account_id | The profile's connected account ID |
| Charges enabled | charges_enabled | Whether the account can accept payments |
| Payouts enabled | payouts_enabled | Whether the account can receive payouts |
| Transfers capability | capabilities.transfers | Must be active for destination charges |
| Details submitted | details_submitted | Whether onboarding was completed |
| Outstanding requirements (due now) | requirements.currently_due | Items Stripe needs immediately; non-empty is one of the triggers for "urgent" status |
| Outstanding requirements (due later) | requirements.eventually_due | Items Stripe will need eventually; contributes to the "has upcoming items" state |
| Upcoming requirements (currently due) | requirements.future_currently_due | From Stripe's future_requirements block -- items tied to a future deadline, not yet live in currently_due |
| Upcoming requirements (eventually due) | requirements.future_eventually_due | From Stripe's future_requirements block -- longer-horizon upcoming items |
| Requirements deadline | requirements.current_deadline (falls back to requirements.future_deadline) | The deadline shown on the alert card ("Deadline: {date}") |
| Pending verification | requirements.pending_verification | Items Stripe has received and is actively reviewing |
| Disabled reason | requirements.disabled_reason | Stripe's stated reason the account is restricted, if any |
| Payment-method capabilities | capabilities.* | 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_duenon-empty,disabled_reasonset,charges_enabled/payouts_enabledfalse, acurrent_deadlineset, or anyeventually_due/future_currently_due/future_eventually_dueitems.is_blocked-- true ifcharges_enabledorpayouts_enabledis false, ordisabled_reasonis set. Drives the "Action required" state.has_upcoming-- true if there are future/eventually_dueitems but nothing urgent (has_issuestrue andis_blockedfalse). 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 nolive_modetoggle 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-locationpayment_test_modeflag.
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 confirmedIf 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 cycleFor 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_paymentsandtransferscapabilities (plusbancontact_paymentsfor Belgium). An account is ready for online-ordering payments only whencharges_enabled,payouts_enabled, and thetransferscapability are all active. - Stripe's
future_requirements(items tied to a future deadline, not yet live inrequirements.currently_due/eventually_due) are surfaced too, asfuture_currently_due/future_eventually_due/future_deadlineon the payment-profile resource. The profile's alert card itemises requirements by falling throughcurrently_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 theeuregion. - Webhooks hit
/api/stripe-webhook/{countryCode}and are signature-verified against per-regionwebhook_secret(platform) andwebhook_secret_oo(online ordering); events are processed byStripeWebhookOrchestrator(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_modeflag, not a Stripe-levellive_modetoggle. - A Stripe Terminal in-person flow exists in the codebase (a
terminal.reader.action_succeededwebhook handler keyed on a device'sstripe_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 thetransferscapability 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_enabledandpayouts_enabledare true), but Stripe has requirements due by a future deadline (fromeventually_due, or from Stripe'sfuture_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 Stripelive_modetoggle. - 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:
- Connected account onboarding incomplete (
details_submittedfalse) - Account restricted (
charges_enabled/payouts_enabledfalse, or adisabled_reason) transferscapability not active (required for destination charges)- Card declined on the customer's side
- Location is in test mode when a live charge was expected
Solutions:
- Complete onboarding on the profile detail page
- Resolve outstanding requirements in the embedded Stripe panel
- Ensure the transfers capability is active (Upvendo can re-request it)
- Ask the customer to try a different card
- Check the location's
payment_test_modesetting
Problem: A local method (e.g. Bancontact) isn't available
Causes:
- The corresponding capability is not active on the connected account
- Business website not set (Stripe requires it before enabling some EU methods)
Solutions:
- Open the profile detail page and resolve the method's capability via the embedded Stripe panel
- 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_blockedis true: charges or payouts are disabled, or Stripe set adisabled_reason. Payments/payouts may be disrupted until resolved. - Verification upcoming --
has_upcomingis true: nothing is blocking yet, but there areeventually_due,future_currently_due, orfuture_eventually_dueitems 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:
- Onboarding never completed (
details_submittedis false) - Outstanding requirements (
requirements.currently_due) -- e.g. ID verification, business details, a bank account - Account restricted, so
charges_enabledand/orpayouts_enabledare false - An approaching deadline for items Stripe will eventually need (
eventually_due, orfuture_currently_due/future_eventually_duefrom Stripe'sfuture_requirements)
Solutions:
- Open the profile detail page and complete the steps shown in the embedded Stripe panel / notification banner
- Provide every item Stripe lists as outstanding before the stated deadline
- The account is ready once
charges_enabled,payouts_enabled, and thetransferscapability 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
}