Ghiduri practice
Abonamente pentru un SaaS
Catalog, înscriere, încasare inițială, reînnoiri și controlul accesului, fără un portal de plată separat.
1. Definește produsul și politica de preț
Separă produsul comercial de prețul recurent. Creează produsul și un preț cu monedă, interval și model de calcul. Înregistrează priceId în catalogul serverului tău; nu accepta orice preț trimis de browser. Poți folosi per_unit, volume sau graduated conform schemei API, iar prices.quote calculează oferta pentru cantitatea cerută. O ofertă nu este o plată și nu activează accesul.
- Folosește valori întregi în subdiviziunile monedei pentru sumele Payments.
- Nu recalcula pe client treptele de tarifare ca sursă de adevăr.
- Prețurile noi și schimbările contractuale trebuie prezentate clientului înainte de confirmare.
- Pentru price_data verifică schema completă din referință; nu îl trimite simultan cu price.
2. Creează abonamentul și deschide Checkout
customerId și priceId provin din aceeași instalare și același mediu. Creează clientul o singură dată pentru identitatea locală, păstrează asocierea și reutilizeaz-o. signupId trebuie persistat; generarea unui ID nou la fiecare refresh anulează protecția de idempotency. Exemplul este pentru încasarea proprie; distribuțiile recurente se declară separat la crearea abonamentului.
import { MonedoPayments } from '@monedo/payments';
// Customer and price IDs must belong to the same installation and environment.
// Persist signupId; retries must reuse the same IDs, quantity and return URLs.
export async function createSubscriptionCheckout({ apiKey, signupId, customerId, priceId, quantity = 1, returnOrigin, fetch }) {
const monedo = new MonedoPayments({ apiKey, fetch });
const subscription = await monedo.subscriptions.create({
customer: customerId,
price: priceId,
quantity,
}, { idempotencyKey: `signup_${signupId}_subscription_v1` });
const session = await monedo.checkoutSessions.create({
subscription: subscription.id,
success_url: `${returnOrigin}/subscription/return`,
cancel_url: `${returnOrigin}/subscription/cancel`,
}, { idempotencyKey: `signup_${signupId}_checkout_v1` });
if (!session.checkout_url) throw new Error('Checkout URL unavailable; retrieve the session before retrying');
return { subscriptionId: subscription.id, sessionId: session.id, url: session.checkout_url };
}
// Update access from the retrieved subscription's `entitled` and `access_until`.
// Do not grant permanent access from a redirect or from the first payment alone.
3. Leagă accesul de starea abonamentului
Păstrează subscriptionId pe contul utilizatorului și citește entitled împreună cu access_until. Nu acorda acces nelimitat pentru că prima plată a reușit. Evenimentele și recitirea abonamentului actualizează drepturile la activare, reînnoire, recuperare și anulare. O citire fără succes este o problemă temporară de sincronizare, nu dovada că utilizatorul și-a anulat contractul.
| Situație | Comportament în aplicație |
|---|---|
| Înscriere incompletă | Afișezi reluarea checkoutului; nu promiți acces activ. |
| Plată în procesare | Aștepți confirmarea și păstrezi operația identificabilă. |
| Reînnoire nereușită | Arăți starea actuală și opțiunea de recuperare; respecți entitled/access_until. |
| Anulare la final de perioadă | Explici data încetării; nu retragi automat accesul deja plătit. |
| Schimbare de plan | Creezi o schimbare și obții acceptarea noilor condiții, nu suprascrii unilateral contractul. |
4. Testează ciclul, nu doar prima plată
- Prima plată cu și fără autentificare suplimentară.
- Reînnoire reușită, reînnoire refuzată și recuperarea facturii restante.
- Anulare la sfârșitul perioadei și efectul asupra accesului.
- Schimbare de preț/cantitate și consimțământul clientului.
- Evenimente duplicate, întârziate și venite în ordine inversă.
- Refundul unei plăți și anularea abonamentului sunt operații distincte; testează ambele.