Integration guide

A Stripe Checkout integration that never trusts the browser.

A new SaaS product needs one server credential, one approved origin, registered SKUs, and a callback endpoint. Stripe opens in the original-site modal by default; the browser never chooses a price, user identity, provider route, or return URL.

Before the first request

Start the Stripe Checkout integration at the product boundary.

Yito Pay onboarding creates an app ID and a one-time server API key. The app record stores the product name, approved return origin, optional entitlement callback URL, provider switches, and merchant route. Each exact hostname that displays Embedded Checkout is registered separately and every sellable SKU maps to a verified Stripe Price, payment mode, currency, and entitlement key. PayPal subscriptions additionally require an approved PayPal Plan.

Store the API key only in the product backend’s secret manager. Do not place it in a browser bundle, public environment variable, mobile application, analytics event, or support screenshot. Yito Pay stores only a keyed digest of the credential, so a lost key must be replaced rather than recovered.

  • Choose a stable app ID and customer identity.
  • Approve exact production and staging origins separately.
  • Create Prices in the same Stripe account assigned to the app.
  • Define the entitlement granted by each SKU.
  • Store the callback secret in the receiving backend.

Required product secrets

A small server-side configuration.

Product environment
YITO_PAY_BASE_URL=https://pay.yito.ai
YITO_PAY_APP_KEY=yp_live_••••••••
YITO_PAY_CALLBACK_SECRET=••••••••
YITO_PAY_CHECKOUT_PROVIDER=stripe

Stripe and PayPal server credentials and webhook verification values remain inside Yito Pay. Individual products do not receive them.

Step 1

Create Checkout from the authenticated product server.

After confirming the signed-in user, generate a unique external order ID and call the Yito Pay API with the registered SKU. Reuse that same external order ID when retrying the request. A different user, SKU, or quantity under an existing ID is rejected visibly.

Server request
const response = await fetch(
  'https://pay.yito.ai/v1/checkout-sessions',
  {
    method: 'POST',
    headers: {
      'authorization': 'Bearer ' + process.env.YITO_PAY_APP_KEY,
      'content-type': 'application/json',
      'idempotency-key': externalOrderId
    },
    body: JSON.stringify({
      external_order_id: externalOrderId,
      external_user_id: authenticatedUser.id,
      sku: 'pro_monthly',
      provider: 'stripe',
      quantity: 1
    })
  }
);

if (!response.ok) {
  throw new Error('Yito Pay returned ' + response.status);
}

const checkout = await response.json();

The response identifies the selected provider and returns only the browser configuration for that provider plus a short-lived recovery URL. Treat every checkout credential as sensitive session material and do not log it.

Step 2

Mount checkout in the product modal.

Load the shared widget once and open it only after the server creates the order. For Stripe, the widget mounts Embedded Checkout directly in the original product page, never through a nested Yito Pay iframe. Eligible customers can use Link inside that same Stripe iframe without a separate Link SDK. For an explicitly enabled PayPal route, the same modal shell starts PayPal JS v6 and PayPal controls whether approval uses a popup, provider modal, or required redirect.

Browser integration
<script src="https://pay.yito.ai/sdk/v1/pay-widget.js"></script>
<div id="checkout" role="dialog" aria-modal="true"></div>
<script>
  const payment = await window.YitoPay.open({
    provider: checkout.provider,
    publishableKey: checkout.publishable_key,
    clientSecret: checkout.client_secret,
    statusUrl: '/api/billing/orders/' + checkout.order_id,
    onConfirmed: () => location.assign('/billing/success')
  });
</script>

The completion callback starts order polling; it does not grant access. If the provider UI cannot initialize, offer the returned recovery URL as an explicit top-level action rather than redirecting automatically.

Step 3

Poll status for customer feedback.

The product backend can query GET /v1/orders/:orderId with its app key. Show a confirmed state only when the API returns paid or the product has processed a valid entitlement callback. A processing state should explain that payment confirmation is still underway instead of pretending the order failed.

Do not poll forever. After a short browser window, send the customer to a pending screen that can be refreshed safely. The verified provider webhook continues working even if the browser is gone.

Step 4

Verify the signed product callback.

Read the callback body as raw text, reject timestamps outside the allowed tolerance, calculate HMAC-SHA256 over timestamp.rawBody, and compare signatures in constant time. Store X-Yito-Pay-Event-Id before granting or revoking access, and select the prepared per-app secret using X-Yito-Pay-Key-Version.

Return a successful response only after the durable entitlement update is committed. A non-success response triggers a later delivery attempt with the same event ID.

Operational endpoints

Use server-only routes for billing actions.

Subscription-management and refund requests require the app credential and validated product user or administrator context. They must not be exposed as unauthenticated browser calls. Health checks are public and reveal only service readiness.

Order status

GET /v1/orders/:orderId returns the app-scoped order state without exposing provider secrets.

Subscription management

POST /v1/customer-portal-sessions uses the stored subscription provider to open Stripe Billing Portal or PayPal Automatic Payments.

Refund request

POST /v1/refunds uses the stored order provider and a deterministic idempotency key.

Questions, answered

Frequently asked questions

Can a product send an amount instead of a SKU?

No. The registered SKU is the only pricing input. Yito Pay loads the registered provider Price or Plan, currency, payment mode, and entitlement from its server-side catalog.

Where should the Yito Pay app key be stored?

Store it in the product backend secret manager. Never send it to a browser or embed it in a client bundle. The browser should call the authenticated product backend first.

What should happen if Embedded Checkout cannot load?

Open the recovery URL returned with the checkout response as a top-level page. It uses the same order boundary without creating a nested iframe.

Can Stripe Link be used inside the original-site modal?

Yes. When Link is enabled for the selected Stripe account and the buyer is eligible, Stripe can show Link inside the same Embedded Checkout iframe. No separate Link SDK is required, and card checkout still works when Link is unavailable or disabled.

Is PayPal already live for every app?

No. PayPal support is implemented behind global and per-app switches, but it must have matching credentials, a verified webhook, catalog mappings, and Sandbox acceptance before an approved Shinstar-routed app can use it.

Build on a reliable payment boundary

Give every SaaS product one secure way to charge.

Start with the integration guide, then register the app, domain, and server credentials before enabling checkout.