Live demo — runs entirely on mock data, no backend or API keys

Add payments to your app
in minutes, not weeks

PayCan gives your application a unified API for one-time and recurring payments through Stripe and PayPal — plus drop-in web components for checkout, plans, orders and subscriptions. Everything on this page uses the real SDK.

Get started in 3 steps Star us on GitHub
Quick start

Up and running in 3 steps

One npm package (or a hosted ES module), one tiny backend endpoint, one line to open a checkout. Each step is tagged with where the code runs: Frontend · browser or Backend · your server.

Don't have a PayCan instance yet? Clone the repo, run composer install && npm install, then php artisan paycan:install (or visit /install in your browser) — the wizard sets up the database and admin account for you.

1

Install the SDK

Frontend · browser
Or hosted — any HTML page
<script type="module">
  import { PayCan } from 'https://pay.yourapp.com/sdk/paycan-sdk.js';
</script>
2

Add a token endpoint to your backend

Backend · your server

This is the only backend code you need. It runs on your server, verifies the user against your login session, and exchanges them for a PayCan user token using your secret API key — which never leaves the server. Never trust a user ID from the request body.

server.js
app.post('/api/paycan/token', requireAuth, async (req, res) => {
  const response = await fetch(`${process.env.PAYCAN_URL}/api/admin/users/sync`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': process.env.PAYCAN_API_SECRET, // secret key: backend only!
    },
    body: JSON.stringify({
      user_id: req.session.userId, // from YOUR session — not the request body
      user: { name: req.user.name, email: req.user.email },
    }),
  });

  const { token } = await response.json();
  res.json({ token }); // return ONLY the token to the browser
});
3

Initialize and sell

Frontend · browser

Point the SDK at your PayCan instance, fetch a token from the endpoint you built in Step 2, and open a checkout with one call. Guests can check out too — no token required.

app.js
import { PayCan } from '@paycan/sdk';

// apiUrl = your PayCan instance (where the payment platform is hosted)
const paycan = new PayCan({ apiUrl: 'https://pay.yourapp.com' });

// The token comes from YOUR backend — the endpoint you built in Step 2.
// The browser only ever sees this short-lived user token, never the secret key.
// Optional: without it, checkouts still work as guest (email is asked instead).
const { token } = await (await fetch('/api/paycan/token')).json();
paycan.setUserToken(token);

// One line to sell something
paycan.openCheckoutModal('prod_pro', {
  onSuccess: (checkoutUrl) => (window.location.href = checkoutUrl),
});
Where does the token come from? fetch('/api/paycan/token') calls your own backend (Step 2), which authenticates the user via your normal session, then asks PayCan for a user-scoped token using the secret key. The flow is: browser → your backend → PayCan → your backend → browser. The token only grants access to that user's own data and refreshes automatically before it expires.
Where does this code go? Create the PayCan instance once, at app startup. Call setUserToken() right after your login flow succeeds — or on page load when the user already has a session. It must run before any account action (orders, subscriptions, transactions); doing it eagerly at login is better than lazily before a payment, so the token is ready the moment the user clicks "Buy". If they aren't signed in, skip it — the checkout modal falls back to guest mode and asks for an email instead.
Web components

Drop-in UI — try each one live Frontend · browser

Framework-agnostic modals rendered in a Shadow DOM, so your page styles are never affected. Fully responsive, with light, dark and auto themes.

Turn the switch off to see guest checkout — the modal asks for an email instead.

CheckoutModal

guest OK

Price + gateway selection and checkout session creation for a product. Shows an email field automatically for guests.

paycan.openCheckoutModal('prod_pro', {
  theme: 'auto',
  onSuccess: (url) => (location.href = url),
});

ProductsModal

guest OK

Browse products or plans and jump straight into checkout. Filter by type: subscription, digital, service, physical.

paycan.openProductsModal({
  type: 'subscription', // optional filter
  theme: 'auto',
});

SubscriptionsModal

auth

List, cancel, resume and change plans. Try cancelling the active plan — the mock data updates live.

import { SubscriptionsModal } from '@paycan/sdk';

new SubscriptionsModal(paycan, { theme: 'auto' }).open();

OrdersModal

auth

Order history with status, totals and gateway. Complete a demo checkout first and your new order appears here.

import { OrdersModal } from '@paycan/sdk';

new OrdersModal(paycan, { theme: 'auto' }).open();

TransactionsModal

auth

Payment history — charges and refunds across all gateways, linked to their orders.

import { TransactionsModal } from '@paycan/sdk';

new TransactionsModal(paycan, { theme: 'auto' }).open();
API playground

Prefer your own UI? Use the API client Frontend · browser

Every modal above is built on the same typed API client you get with the SDK — filtering, sorting, includes and pagination included. Click a call to run it against the mock API.

Public — no auth needed
const products = await paycan.products.list({ include: 'prices' });
// Click "Run" to execute the selected call against the mock API
Tip: window.paycan is exposed in this page — try calls in the browser console too.
Access control

Did this user buy it? Check access in one call Frontend · browser

The most common thing to build right after checkout: gate content behind a purchase. One-time products (e-books, services, physical goods) are unlocked by a completed order; plans are unlocked by an active subscription. Both checks are cheap enough to run on every page load.

Orders — e-books, services, physical goods
async function ownsProduct(productId) {
  const { data: orders } = await paycan.orders.list({
    filter: { status: 'completed' },
    include: 'product,productPrice',
  });

  return orders.some((order) => order.product?.id === productId);
}

if (await ownsProduct('prod_ebook')) {
  unlockDownload();
} else {
  showBuyButton();
}

PayCan is open source — and stars keep it moving ⭐

If this demo saved you an afternoon of payment plumbing, a GitHub star is the single most helpful thing you can do. It's how new developers find the project and how we prioritize the roadmap.