Quick Start
Three steps to generate a Stripe payment link from any HTML page:
<!-- 1. Include the SDK -->
<script src="https://jpaypayment.web.app/jpay.js"></script>
<script>
// 2. Configure (once, on page load)
JPay.init({
companyId: 'YOUR_COMPANY_DOC_ID', // Firestore companies doc ID
});
// 3. Create a payment link
async function chargeClient() {
const { url } = await JPay.createPaymentLink(
150, // amount in dollars
'client-uid-123', // your reference (customer ID, order ID, etc.)
{
productName: 'Consultation Fee',
type: 'one-time',
currency: 'usd',
}
);
// url → https://buy.stripe.com/xxx
window.open(url);
}
</script>
Installation
Browser (script tag)
<script src="https://jpaypayment.web.app/jpay.js"></script>
Exposes a global JPay object. No build step required.
ES Module
// The same file works as an ES module — wrap in your own module
import 'https://jpaypayment.web.app/jpay.js';
// JPay is now on window.JPay
Node.js / non-browser
Use the REST API directly (see REST API section). The jpay.js file uses window.fetch so it targets browsers, but the underlying Cloud Functions accept any HTTP client.
JPay.init( config )
Set defaults so you don't have to repeat them in every call. Call once on page load, before any other JPay function.
| Field | Type | Required | Description |
|---|---|---|---|
| companyId | string | optional | Default Firestore companies doc ID. Used when payDetails.companyId is omitted. |
| apiKey | string | optional | API key if you have configured one (see API Key Setup). Leave empty if not set up. |
JPay.createPaymentLink( amount, referenceUID, payDetails )
Creates a Stripe Payment Link, saves the record to Firestore, and returns the hosted checkout URL. The customer visits the URL to complete payment — Stripe handles the checkout page.
amount: number,
referenceUID: string,
payDetails: PayDetails
) → Promise<CreateResult>
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| amount | number | required | Amount in dollars. Minimum 0.50. e.g. 150 = $150.00 |
| referenceUID | string | required | Your internal reference — a customer UID, order ID, invoice number, etc. Stored on the Firestore record and used to look up links later. |
| payDetails | object | required | Payment configuration. See fields below. |
payDetails fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| companyId | string | optional* | JPay.init default | Firestore companies doc ID. Falls back to value set in JPay.init(). |
| targetDocId | string | optional | referenceUID | Write-back target. The exact document ID in the requesting project's collection that JPay updates when the payment completes. Defaults to referenceUID. See Cross-Project Write-Back. |
| productId | string | optional† | — | Existing Stripe product ID (e.g. 'prod_Qx1234...'). Use this to attach the link to an existing product in your Stripe catalog. |
| productName | string | optional† | — | Name of a new product to create in Stripe. Required if productId is not provided. |
| productDescription | string | optional | — | Description shown on the Stripe checkout page. Only applies when creating a new product. |
| type | string | optional | 'one-time' | 'one-time' for a single charge, 'subscription' for recurring monthly billing. |
| currency | string | optional | 'usd' | ISO currency code: 'usd', 'cad', 'eur', 'gbp', 'aud', etc. |
| note | string | optional | — | Internal note stored in Firestore. Not shown to the customer. Useful for invoice numbers or staff notes. |
† Either productId or productName must be provided.
Returns — CreateResult
true on successExample — existing product
const result = await JPay.createPaymentLink(
250,
customer.uid,
{
productId: 'prod_Qx9AbcDef123', // existing Stripe product
type: 'one-time',
currency: 'usd',
note: `Invoice #${invoiceNum}`,
}
);
console.log(result.url); // https://buy.stripe.com/xxx
console.log(result.linkId); // Firestore doc ID
Example — new product
const { url, linkId } = await JPay.createPaymentLink(
99,
order.id,
{
productName: 'Monthly Support Plan',
productDescription: 'Includes unlimited email support and monthly check-in call.',
type: 'subscription',
currency: 'cad',
}
);
// Open the checkout page in a new tab
window.open(url, '_blank');
Example — full error handling
async function generateLink(customerId, amount) {
try {
const { url } = await JPay.createPaymentLink(amount, customerId, {
productName: 'Service Fee',
type: 'one-time',
});
navigator.clipboard.writeText(url);
alert('Link copied to clipboard!');
} catch (err) {
alert('Error: ' + err.message);
}
}
JPay.getPaymentLinks( referenceUID )
Fetches all payment links that were previously created for a given referenceUID. Use this to check payment status or display link history for a customer record.
Returns — GetResult
true on successLink record fields
- linkIdstringFirestore document ID
- urlstringThe Stripe Payment Link URL
- amountnumberAmount in dollars
- currencystringISO currency code
- typestring'one-time' | 'subscription'
- descriptionstringProduct name
- notestringInternal note
- activebooleanWhether the link is still active
- paymentsCountnumberHow many payments have been made through this link
- paymentsarrayArray of payment records (see below)
- createdAtstringISO timestamp
Payment record fields (inside payments[])
- customerNamestring|nullName entered on Stripe checkout
- customerEmailstring|nullEmail entered on Stripe checkout
- customerPhonestring|nullPhone entered on Stripe checkout
- billingAddressobject|nullBilling address from Stripe checkout
- amountnumberAmount actually paid
- currencystringISO currency code
- statusstring'paid' | 'unpaid' | 'no_payment_required'
- paidAtstringISO timestamp of payment
- sessionIdstringStripe checkout session ID
- paymentIntentIdstring|nullStripe PaymentIntent ID (one-time only)
- subscriptionIdstring|nullStripe Subscription ID (subscription only)
Example
const { links } = await JPay.getPaymentLinks(customer.uid);
if (links.length === 0) {
console.log('No payment links for this customer');
}
for (const link of links) {
console.log(link.url, link.paymentsCount, link.amount);
for (const payment of link.payments) {
console.log(payment.customerEmail, payment.paidAt);
}
}
REST API — POST /createPaymentLinkAPI
The underlying Cloud Function endpoint. Use this from Node.js, PHP, Python, or any server that can make HTTP requests.
Request body (JSON)
{
"apiKey": "your-key", // optional if no key configured
"amount": 150,
"referenceUID": "customer-or-order-uid",
"targetDocId": "invoiceDocId", // optional — doc to update on payment (defaults to referenceUID)
"payDetails": {
"companyId": "firestoreCompanyDocId",
"type": "one-time", // or "subscription"
"currency": "usd",
"productId": "prod_xxx", // OR productName below
"productName": "Service Fee",
"productDescription": "Optional description",
"note": "Invoice #42"
}
}
Success response
{
"success": true,
"url": "https://buy.stripe.com/xxx",
"linkId": "firestoreDocId",
"referenceUID": "customer-or-order-uid"
}
Example — PHP
$response = file_get_contents('https://us-central1-jpay-a15a4.cloudfunctions.net/createPaymentLinkAPI', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json',
'content' => json_encode([
'amount' => 150,
'referenceUID' => $customer['uid'],
'payDetails' => [
'companyId' => 'yourCompanyDocId',
'productName' => 'Service Fee',
],
]),
'ignore_errors' => true,
]
]));
$data = json_decode($response, true);
$url = $data['url'];
Example — Node.js
const res = await fetch('https://us-central1-jpay-a15a4.cloudfunctions.net/createPaymentLinkAPI', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: 150, referenceUID: customerId, payDetails: { companyId, productName: 'Fee' } }),
});
const { url } = await res.json();
REST API — GET /getPaymentLinksByRef
Query parameters
| Param | Required | Description |
|---|---|---|
| referenceUID | required | The UID to look up |
| apiKey | optional | API key, if configured |
GET .../getPaymentLinksByRef?referenceUID=customer-uid-123&apiKey=yourkey
Cross-Project Write-Back
JPay can automatically update the requesting project's own Firestore the moment a payment completes — so the project that asked for the link doesn't have to poll. When a customer pays, JPay opens the requesting project (using its service account) and writes the paid status into the document you point it at.
How the loop closes
1. Your project → POST createPaymentLinkAPI { amount, referenceUID, targetDocId, payDetails.companyId }
2. JPay → creates Stripe link, stores paymentLinks doc (with companyId + targetDocId)
3. JPay → you → returns { url }. You send it to the customer.
4. Customer pays → Stripe fires checkout.session.completed → POST /stripeWebhook
5. JPay → records payment, then opens YOUR project via its service account
6. JPay → updates <targetCollection>/<targetDocId> with the paid status
1. Configure the company (one-time)
In the JPay Admin page → Manage Companies, each company that needs write-back must store two extra things:
- Service Account JSONobjectThe requesting project's service account (Firebase Console → Project Settings → Service Accounts → Generate new private key). Grants JPay write access.
- Payment CollectionstringThe collection in the requesting project to update, e.g.
invoices.
Existing companies can be updated with the new Edit button. Leaving the Stripe key or service account blank when editing keeps the existing value.
2. Send the target doc ID per request
The requesting project owns the identity. Pass the doc ID of the record you want flagged as paid — as targetDocId, or simply as referenceUID (JPay uses referenceUID when targetDocId is omitted). No ID matching between JPay and your project is needed.
await JPay.createPaymentLink(150, 'VD-314277', {
companyId: 'yourCompanyDocId',
productName: 'Invoice VD-314277',
targetDocId: 'VD-314277', // → updates invoices/VD-314277 on payment
});
3. What JPay writes into your document
On payment, JPay set(..., { merge: true })s these fields onto <targetCollection>/<targetDocId> — your existing fields are preserved:
{
"jpayPaid": true,
"jpayStatus": "paid",
"jpayAmount": 150.00,
"jpayCurrency": "usd",
"jpayCustomerEmail": "customer@example.com",
"jpayCustomerName": "Jane Doe",
"jpayPaidAt": "2026-09-02T17:05:03.000Z",
"jpayPayment": { /* full payment record */ },
"jpayUpdatedAt": "2026-09-02T17:05:04.000Z"
}
writeBackStatus: 'ok' (or 'error' + writeBackError) back on its own paymentLinks doc, so you can confirm delivery.merge, an unknown targetDocId creates a new doc with only the jpay* fields. Always send the ID of your existing record. Also: Stripe webhooks are per-mode — a sk_test_ key needs the webhook registered under Stripe Test mode too, or nothing is written back.Firestore Schema
Collection: paymentLinks
Every generated link is stored here. Readable in the JPay Admin page.
- companyIdstringFirestore companies doc ID
- companyNamestringDisplay name of the company
- typestring'one-time' | 'subscription'
- amountnumberAmount in dollars
- currencystringISO currency code (lowercase)
- descriptionstringProduct name
- productDescriptionstringProduct description (if provided)
- productIdstringStripe product ID
- notestringInternal note
- referenceUIDstring|nullCross-project reference UID passed by caller
- targetDocIdstring|nullDoc ID updated in the requesting project on payment (defaults to referenceUID)
- sourcestring'admin' (created via admin page) | 'api' (created via SDK/REST)
- stripePaymentLinkIdstringStripe's Payment Link ID (e.g.
plink_xxx) - urlstringThe Stripe checkout URL
- activebooleanWhether the link is active
- paymentsarrayPayment records appended by webhook on each payment
- writeBackStatusstring'ok' | 'error' — result of the cross-project write-back
- writeBackErrorstringError message if the write-back failed
- createdAttimestampFirestore server timestamp
Collection: companies
- namestringCompany display name
- stripeKeystringStripe secret key — used server-side to create links
- serviceAccountobjectRequesting project's service-account JSON — lets JPay write the paid status back into that project
- targetProjectIdstringRequesting project's Firebase project ID (read from serviceAccount)
- targetCollectionstringCollection in the requesting project that JPay updates on payment
- createdAttimestampFirestore server timestamp
API Key Setup
By default the endpoints are open to any caller — fine for internal projects. To require an API key:
1. Set the key (run once in your terminal)
firebase functions:config:set jpay.api_key="choose-any-secret-string" --project jpay-a15a4
firebase deploy --only functions --project jpay-a15a4
2. Pass the key in your project
JPay.init({
apiKey: 'choose-any-secret-string',
companyId: 'yourCompanyDocId',
});
createPaymentLinkAPI call from your server (PHP/Node) and never expose the key in the browser.Webhook — Customer Data After Payment
Stripe hosts the checkout page, so you don't see card details — that's PCI compliance. But after each successful payment, Stripe POSTs a checkout.session.completed event to your webhook endpoint, which captures the customer's information and stores it in the payments[] array on the Firestore record.
Webhook URL
Data captured per payment
- customerNamestringName from Stripe checkout
- customerEmailstringEmail from Stripe checkout
- customerPhonestringPhone from Stripe checkout
- billingAddressobjectBilling address from Stripe checkout
- amountnumberAmount actually paid
- statusstring'paid' for successful payments
- paidAtstringISO timestamp
Error Handling
Both createPaymentLink() and getPaymentLinks() throw a standard Error on failure. Always wrap calls in try/catch.
try {
const { url } = await JPay.createPaymentLink(amount, uid, payDetails);
// success
} catch (err) {
// err.message contains a human-readable description
console.error(err.message);
}
Common error messages
| Message | Cause |
|---|---|
| payDetails.companyId is required | No companyId in payDetails and no default set via JPay.init() |
| payDetails.productId or payDetails.productName is required | Neither was provided |
| Minimum amount is $0.50 | Amount below Stripe's minimum |
| Company not found: xxx | The companyId doesn't exist in Firestore |
| Invalid API key | API key mismatch (only when a key is configured) |
JPay SDK · powered by Stripe + Firebase · jpaypayment.web.app