Developers

Payments & Stripe

How members add and manage payment methods from your app, how funds settle to your own Stripe account, and how to test the whole flow end-to-end.

The model in one paragraph

Hapana runs payments through Stripe Connect. Your business connects your own Stripe account to Hapana once; from then on, charges are made through Hapana's Connect platform on your account's behalf, and the funds (minus Hapana's platform fee) settle to you. To collect a card you drive a Stripe SetupIntent from your app: Hapana's API creates the SetupIntent and hands you a setupIntentClientSecret; you mount Stripe.js / Elements, confirm the card, then tell Hapana to activate the saved method.

You need a publishable key to confirm the SetupIntent, and it must match the account the intent was created on. Because Hapana's Connect configuration varies by region, don't hard-code your own account's key or guess whether to pass { stripeAccount }. Use the Stripe publishable key Hapana gives you for your site (Control Center → API page lists your pk_test_… / pk_live_…), and only pass a stripeAccount option if the API page tells you your site confirms on a connected account. Mismatching the key/account is what produces "No such setup_intent".

One-time setup

  1. Connect your Stripe account. In Hapana Control Center → Payments, run the Stripe Connect onboarding. This links your Stripe account for settlement and enables the card_payments capability. Until onboarding is complete and that capability is active, live charges will be rejected.
  2. Know your Stripe publishable key. The initiate call (step 1 below) returns the correct publishableKey for your site in the response, so you normally don't need to configure it yourself. The Control Center → API page also lists it for both test (pk_test_…) and live (pk_live_…) mode, plus whether a stripeAccount option is required for your site. Publishable keys are safe to ship in a client app.
  3. Decide the mode per environment. Point your staging/dev builds at the test key and your production build at the live key. The Member API creates the SetupIntent in whichever mode your corporate is currently configured for, so the key you use must be in the matching mode.

Adding a payment method (the embed flow)

Three calls, with a Stripe.js step in the middle:

  1. Initiate. POST /public-api/v2/member/payments/methods returns a setupIntentClientSecret, the new method's id, and the publishableKey to confirm with.
  2. Collect & confirm the card with Stripe.js / Elements, using the publishable key Hapana gives you for your site.
  3. Complete. POST /public-api/v2/member/payments/methods/{id}/complete verifies the SetupIntent succeeded and activates the method, returning the card brand / last4.

1 · Initiate

curl -X POST https://api.hapana-app.com/public-api/v2/member/payments/methods \
  -H "Authorization: Bearer <firebase-id-token>" \
  -H "X-Site-ID: <site-id>"

# → { "object": "payment_method_setup",
#     "id": "<clientPaymentMethodId>",
#     "setupIntentClientSecret": "seti_..._secret_...",
#     "publishableKey": "pk_test_..." }   // use this to confirm (step 2)

2 · Collect & confirm with Stripe.js

// Use the publishableKey returned by the initiate call in step 1.
// Pass { stripeAccount } ONLY if the API page says your site confirms on a
// connected account — otherwise omit it.
const stripe = Stripe(publishableKey /*, { stripeAccount } */)
const elements = stripe.elements({ clientSecret: setupIntentClientSecret })
const paymentElement = elements.create('payment')
paymentElement.mount('#payment-element')

// On submit:
const { setupIntent, error } = await stripe.confirmSetup({
  elements,
  clientSecret: setupIntentClientSecret,
  redirect: 'if_required',
})
if (error) { /* show error.message to the member */ }
// setupIntent.status === 'succeeded' → proceed to step 3

3 · Complete

curl -X POST https://api.hapana-app.com/public-api/v2/member/payments/methods/<id>/complete \
  -H "Authorization: Bearer <firebase-id-token>" \
  -H "X-Site-ID: <site-id>"

# → { "object": "payment_method", "type": "card", "status": "active",
#     "isDefault": true, "card": { "brand": "visa", "last4": "4242", ... } }

Listing, removing & history

CallPurpose
GET /public-api/v2/member/payments/methodsList the member's saved payment methods (brand, last4, default flag).
DELETE /public-api/v2/member/payments/methods/{id}Remove a saved payment method.
GET /public-api/v2/member/payments/historyThe member's payment / billing history.

Testing end-to-end

  • Use test mode. Point your app at Hapana's pk_test_… key and make sure your corporate is in Stripe test mode. No real money moves and you don't need completed live onboarding.
  • Test cards. 4242 4242 4242 4242 (any future expiry, any CVC) succeeds. For 3-D Secure, use 4000 0025 0000 3155. Full list at docs.stripe.com/testing.
  • Confirm the whole flow. Run the three calls above with a test card in the embed, then call GET /public-api/v2/member/payments/methods and confirm the card appears with the expected last4.
  • Automated / headless tests. When you can't drive the Stripe.js UI, confirm the SetupIntent with the Stripe API or the Stripe CLI (using a test payment method like pm_card_visa), then call the /complete endpoint. Ask Hapana to enable the dev-only test helper for your sandbox if you need to script method creation without Stripe.js.
Where the money goes. Every successful charge is a Connect destination charge: the amount (minus Hapana's platform fee) settles to your connected Stripe account, and disputes/refunds are managed against it. You can watch test and live activity in your own Stripe Dashboard once onboarding is complete. Questions on fees or settlement: api-support@hapana.com.