PFlux DOCS
GitHub Integration enquiries Start integrating
PAYMENTS/PAYMENT LINKS

Payment links

An invoice, a price or a subscription you send by e-mail, chat or QR code. No server of your own; the money goes straight to your wallet.

KINDSonce · reusable · subscription
SERVER NEEDEDNone
CREATE<checkout>/#/new
ASSETUSDC on Base
YOUR WALLET → A LINK → THE BUYER’S WALLET → YOUR WALLET

A payment link is a standing offer you send as a URL — by e-mail, in a chat, as a QR code. The buyer opens it in the P2Flux checkout and pays from their own wallet straight to yours through the P2Flux contracts. P2Flux never holds the money.

Overview

Payment links are for merchants without a server of their own. The link itself carries its terms — kind, receiving wallet, amount, expiry, the optional description and, for a subscription, the schedule — signed by P2Flux, like a payment intent. Creating one keeps no record of it.

Every link comes with two tokens:

TOKENOPENSWHO GETS IT
link (p2l1.…)<checkout>/#/link/<link>Your buyers. It is the payment page.
manage (p2lm1.…)<checkout>/#/links/<manage>You only. Your overview: it shows who paid and, for a subscription link, can collect or stop collecting.
KEEP THE MANAGE LINK PRIVATE

Anyone holding it sees who paid and can collect or stop a subscription. It cannot send money anywhere: every payment still goes to the wallet written inside the link.

The three kinds

KINDWHAT THE BUYER GETSVALID FOR
onceAn invoice. Every open mints a payment intent with the same reference, so the contract itself refuses a second payment.Up to 30 days (default 7)
reusableA fixed price, payable any number of times. Each payment’s reference starts with the link’s 16-byte id, which is how its payments are found on chain.Up to a year
subscriptionA plan. The buyer signs once; P2Flux keeps the subscription and collects every period automatically. At least 1 USDC a period, period at least one day; optionally a fixed number of periods.Up to a year, for new signups

When a link expires it takes no new payments or subscriptions. Payments made before then stay valid, and subscriptions already signed keep being collected.

One-time links (once and reusable) can let the buyer pay the network fee in USDC with no ETH: gas_payment_mode: 'payment_token'. You then fund the fixed network fee of 0.10 USDC per payment, as described in Who pays the network fee. The mode is fixed for the link’s life. Fees are otherwise the same as for any one-time or recurring payment — see Fees & gas.

Create a link

Without writing code, open the form on the checkout: pay.p2flux.com/#/new (Base Mainnet), or pay-test.p2flux.com/#/new for the test network. Enter your wallet, the amount and the kind; the page shows the link to send and your private overview.

From code, createPaymentLink in either SDK, or POST /v1/links:

import { createP2Flux } from '@p2flux/sdk'

const p2flux = createP2Flux({ apiUrl: process.env.P2FLUX_API_URL })

const plan = await p2flux.createPaymentLink({
  kind: 'subscription',
  recipient: '0x8fA4…6C21',
  amount: '9.00',
  period: 30 * 86_400,
  periods: 12,                    // omit for until cancelled
  label: 'Monthly support plan',
})

p2flux.checkoutLink('link', plan.link)      // send this to buyers
p2flux.checkoutLink('links', plan.manage)   // your private overview - keep it to yourself
use P2Flux\P2FluxClient;

$p2flux = new P2FluxClient(['apiUrl' => getenv('P2FLUX_API_URL')]);

$invoice = $p2flux->createPaymentLink([
    'kind'      => 'once',
    'recipient' => '0x8fA4…6C21',
    'amount'    => '250.00',
    'label'     => 'Invoice #1042',
]);

$p2flux->checkoutLink('link', $invoice['link']);     // send this to the buyer
$p2flux->checkoutLink('links', $invoice['manage']);  // your private overview
curl -X POST "$P2FLUX_API_URL/v1/links" \
  -H "content-type: application/json" \
  -d '{
    "kind": "reusable",
    "recipient": "0x8fA4bE1a0F2d3C4b5A6978Ee0d1C2b3A4F5e6C21",
    "amount": "15.00",
    "label": "Workshop ticket"
  }'
FIELDTYPEREQUIREDNOTES
kindstringYesonce, reusable or subscription.
recipientstringYesThe wallet that receives every payment.
amountstringYesDecimal USDC — per payment, or per period for a subscription.
labelstringNoA description for the buyer. See The description.
expires_atintegerNoUnix seconds, between 1 hour and 30 days (once) or 366 days (other kinds) from now. Default 7 days for an invoice, a year otherwise.
gas_payment_modestringNoOne-time kinds: native (default) or payment_token.
periodintegerSubscriptionSeconds between charges, at least 86400.
periodsintegerNoSubscription: number of charges (1–1200). Omit for until cancelled.
suspend_after_daysintegerNoSubscription: days without a payment after which a subscriber is paused, 1–90. Default 7.
response
{
  "link": "p2l1.…",
  "manage": "p2lm1.…",
  "kind": "reusable",
  "id": "0x5b0e…91c4",
  "chain_id": 8453,
  "recipient": "0x8fA4bE1a0F2d3C4b5A6978Ee0d1C2b3A4F5e6C21",
  "amount": "15.000000",
  "amount_units": "15000000",
  "label": "Workshop ticket",
  "created_at": 1791100000,
  "expires_at": 1822636000,
  "gas_payment_mode": "native"
}

The response repeats the terms with the two tokens; a subscription link returns period, periods and suspend_after_days instead of gas_payment_mode. Which kinds a deployment offers is its choice: a kind it does not offer is refused with LINK_UNAVAILABLE.

What the buyer sees

The link opens the ordinary payment or subscription screen of the checkout, with your description if you set one. For an invoice that is already paid, the buyer sees that it is paid and nothing more is owed. A buyer who already subscribed through a subscription link opens it again and chooses Manage your subscription to see it, restore their USDC approval or cancel.

THE LINK IS CHECKED AGAINST ITSELF

The checkout reads the terms written inside the link and refuses any API answer that differs — wallet, amount, schedule or description. The buyer is never asked to sign terms other than the ones in the link they were sent.

Your overview

Open <checkout>/#/links/<manage>, or ask paymentLinkStatus({ manage }) in either SDK. What it shows depends on the kind:

KINDWHAT YOU SEE
oncePaid or not, and the payment: payer, transaction, block, amount. When the contract says paid but the transaction cannot be located, only paid is given.
reusableEvery payment, read from the blockchain — listed once its block is safe from reorganisation, usually within a minute. Reading is incremental and remembered; complete: false means there is more to read — ask again (the page has a Refresh button). The list stops at 5,000 payments.
subscriptionThe subscribers: state (active, stopped, suspended or ended), the periods paid, the next attempt and the last answer from a collection.

With the public link instead, the same call returns the terms, whether the link is open or expired and, for an invoice, whether it is paid — but never who paid.

Subscriptions collected for you

With an ordinary recurring payment your server asks for each renewal. With a subscription link P2Flux does it: when a period opens, it collects that period from the buyer’s signed permission. The contract still enforces the signed terms and one charge per period.

When the buyer cannot pay — too little USDC, or the approval removed — P2Flux tries again:

1 HOURFirst retry.
6 HOURSSecond retry.
DAILYThen once a day — only inside the first quarter of the period, and never more than 3 days into it. A daily plan therefore gets one retry, a weekly plan two.
SKIPPEDAfter that the period is skipped. The contract has no catch-up: a skipped period is never charged later.
ENDEDThree periods in a row that the buyer could not pay end the subscription.
PAUSEDNo successful payment for 7 days, for any reason (you choose 1–90 days when you create the link): the subscriber is paused — not ended. P2Flux stops collecting until you press Collect now and it succeeds.

A period missed because of trouble on P2Flux’s side — the network, gas prices, the service itself — is skipped too (the contract cannot charge it later), but it never counts against the buyer. A buyer who cancels shows as ended as soon as the link or your overview is opened, and may subscribe again.

Signing up is paying: the buyer approves USDC for the recurring contract (or, with no ETH, has P2Flux set the approval as part of the signup), signs once, and the first period is charged at once. Only when that first payment has landed (or is confirming) is the subscription kept — so nobody can fill your list with signups that never pay.

WHOACTIONWHAT IT DOES
YouCollect nowCollects the current period at any time within it. When it succeeds it also restarts automatic collection for a stopped subscriber; when it fails nothing changes but the reason shown. Same answers as /v1/charges.
YouStop collectingP2Flux stops collecting from this subscriber. Soft and reversible: a successful Collect now restarts it. The buyer’s signed permission stays on chain.
The buyerCancelOnly the buyer’s wallet can revoke the permission on chain. They open the link again and choose Manage your subscription.

The description

The optional label is shown to the buyer under the heading “Note from the link’s creator · not verified by P2Flux”. It is your text, not P2Flux’s, and the page says so.

  • Up to 60 characters.
  • Letters of one writing system, ASCII digits, currency signs, spaces and . , : ; ' ( ) # & + _ ! ? % - /.
  • Nothing that reads as a web or e-mail address — so no letter right after a full stop (write “Mr Smith”, not “Mr. Smith”) — no invisible or look-alike characters, and no mention of P2Flux or “verified”.
  • Do not put sensitive or unnecessary personal data in it: anyone with the link can read it.

What P2Flux stores

P2Flux keeps no record of a link merely because it was created or opened — its terms are in the link. (Ordinary technical and security logs still apply.) P2Flux keeps two things:

  • A small cache of what the blockchain already answered about a link, so the same blocks are not read twice.
  • For subscription links only: the buyer’s signed permission, plus the wallets, the amount and the period — what is needed to collect renewals without a merchant server.

No names and no e-mail addresses. A record is created only once the first payment has landed, and is deleted 30 days after the subscription ended.

Self-hosted checkout

The link screens are part of the self-hosted checkout from release 1.1.0: #/link/, #/links/ and the create form #/new work on your own copy too. With a recipients wallet list in config.js, the page only opens and creates links that pay your own wallets.

The SDKs build links to your copy with the checkoutUrl option: checkoutLink('link', token) and checkoutLink('links', token) need @p2flux/sdk 0.10.0, p2flux/sdk-php 0.10.0 or p2flux/laravel 0.4.0.

API

Six endpoints, all POST with a JSON body and no authentication; the token in the body is what authorizes the call. Full fields in the API reference.

ENDPOINTSDKWHAT IT DOES
/v1/linkscreatePaymentLinkCreate a link. No record is kept.
/v1/links/openopenPaymentLinkWhat the checkout calls when a buyer opens a link: an intent for one-time kinds, a 15-minute setup token for a subscription.
/v1/links/statuspaymentLinkStatusTerms and what the link has collected — with exactly one of link or manage.
/v1/links/subscribesubscribePaymentLinkFor a checkout of your own: join a subscription link with the signed permission; charges the first period.
/v1/links/collectcollectPaymentLinkCollect a subscriber’s current period now.
/v1/links/stopstopPaymentLinkStop collecting from a subscriber.

Errors

CODEHTTPMEANING
INVALID_LINK400Not a link this deployment signed — or a test-network link on the production checkout, and the reverse.
LINK_EXPIRED400The link’s date has passed. Payments made before then stay valid; ask the merchant for a new link.
LINK_UNAVAILABLE409The link cannot be used right now; reason says why: KIND_NOT_OFFERED, CONTRACT_CHANGED (the contract an invoice was made for has been replaced) or SUBSCRIPTIONS_FULL.
ALREADY_SUBSCRIBED409This wallet already has a subscription through this link. A second signature would mean a second debit.

All four carry the action INVALID_REQUEST: the same request gets the same answer. See Errors.

Something more than a standard integration?
Marketplace flows, platform billing and custom settlement logic.
Discuss an integration