Plan subskrypcji umożliwia sprzedawcy publikowanie warunków rozliczeniowych, które użytkownicy mogą zaakceptować. Po zasubskrybowaniu przez użytkownika, sprzedawca lub upoważniony pobierający może pobierać do kwoty określonej w planie w każdym okresie rozliczeniowym.
Ten przewodnik przedstawia pełny przepływ jako elementy składowe. Sprzedawca tworzy plan, subskrybent go akceptuje, a sprzedawca lub pobierający pobiera płatności z wynikowego PDA subskrypcji.
Instalacja
pnpm add @solana/subscriptions @solana/kit @solana/kit-plugin-rpc @solana/kit-plugin-signer @solana-program/token
Utworzenie planu
Sprzedawca jest właścicielem planu. PDA planu jest wyprowadzane z adresu
sprzedawcy i planId.
Sponsor może sfinansować rent planu, przekazując opcjonalny parametr payer, podczas gdy merchant pozostaje właścicielem planu. Usunięcie planu zwraca środki właścicielowi, a nie płatnikowi, dlatego sponsorowanie należy kontrolować poza łańcuchem.
import { address, createClient } from '@solana/kit';import { solanaLocalRpc } from '@solana/kit-plugin-rpc';import { signer } from '@solana/kit-plugin-signer';import { findPlanPda, subscriptionsProgram } from '@solana/subscriptions';const merchantClient = createClient().use(signer(merchantSigner)).use(solanaLocalRpc({ rpcUrl: 'http://127.0.0.1:8899' })).use(subscriptionsProgram());const planId = 1n;const tokenMint = address('TOKEN_MINT_ADDRESS_HERE');const amount = 5_000_000n;const periodHours = 720n;const metadataUri = 'https://example.com/plan.json';const destinations = [merchantSigner.address];const pullers = [address('PULLER_WALLET_ADDRESS_HERE')];await merchantClient.subscriptions.instructions.createPlan({planId,mint: tokenMint,amount,periodHours,endTs: 0n,destinations,pullers,metadataUri,}).sendTransaction();const [planPda] = await findPlanPda({owner: merchantSigner.address,planId,});
Aktualizacja planu
Sprzedawca może aktualizować modyfikowalne pola planu po jego utworzeniu. Dotychczasowi subskrybenci zachowują warunki, które zaakceptowali, podczas gdy nowi subskrybenci akceptują aktualne warunki planu.
Obowiązuje kilka zasad:
- Skończony
endTsmożna jedynie skrócić, nigdy wydłużyć ani wyczyścić (PlanEndTsCannotExtend). - W planie
Sunsetmożna usunąć pullery (nowy zestaw musi być podzbiorem bieżącego), aby unieważnić przejęty puller; status,endTsi metadane pozostają niezmienione. - Edycje są możliwe w trakcie ostatniego okresu rozliczeniowego planu, o ile
endTspozostaje bez zmian. - Instrukcja zawiera stan planu zaobserwowany w momencie podpisywania
(
expectedCreatedAt,expectedEndTs,expectedPullers,expectedMetadataUri). Jeśli aktualny plan nie pasuje już do zapisanego stanu, aktualizacja zostaje odrzucona (StalePlanApproval), więc nieaktualna podpisana aktualizacja nie może przywrócić usuniętych pullerów ani cofnąć późniejszych edycji. MetodaupdatePlanklienta wtyczki pobiera dla Ciebie aktualny stan; podczas ręcznego budowania należy samodzielnie pobrać plan i przekazać odpowiednie pola.
import { PlanStatus } from '@solana/subscriptions';const updatedMetadataUri = 'https://example.com/updated-plan.json';const updatedPullers = [address('NEW_PULLER_WALLET_ADDRESS_HERE')];await merchantClient.subscriptions.instructions.updatePlan({owner: merchantSigner,planPda,status: PlanStatus.Active,endTs: 0n,pullers: updatedPullers,metadataUri: updatedMetadataUri,}).sendTransaction();
Subskrybuj
Subskrybent akceptuje warunki bieżącego planu. PDA subskrypcji jest wyprowadzany z PDA planu i adresu subskrybenta.
Aby połączyć inicjalizację authority i subskrypcję w jedną transakcję, przekaż wartość sentinel UNKNOWN_INIT_ID (eksportowaną przez TypeScript SDK) jako expectedSubscriptionAuthorityInitId. Program akceptuje authority tylko wtedy, gdy zostało ono utworzone w bieżącym slot, więc sentinel działa przy nowych rejestracjach; powracający użytkownik, którego authority zostało utworzone we wcześniejszym slot, musi przekazać prawdziwy initId (klient wtyczki pobiera go automatycznie), w przeciwnym razie wywołanie zakończy się błędem StaleSubscriptionAuthority.
import { createClient } from '@solana/kit';import { solanaLocalRpc } from '@solana/kit-plugin-rpc';import { signer } from '@solana/kit-plugin-signer';import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';import {fetchMaybeSubscriptionAuthority,findSubscriptionAuthorityPda,findSubscriptionDelegationPda,subscriptionsProgram,} from '@solana/subscriptions';const subscriberClient = createClient().use(signer(subscriberSigner)).use(solanaLocalRpc({ rpcUrl: 'http://127.0.0.1:8899' })).use(subscriptionsProgram());const [subscriberAta] = await findAssociatedTokenPda({mint: tokenMint,owner: subscriberSigner.address,tokenProgram: TOKEN_PROGRAM_ADDRESS,});const [subscriptionAuthorityPda] = await findSubscriptionAuthorityPda({user: subscriberSigner.address,tokenMint,});const subscriptionAuthority = await fetchMaybeSubscriptionAuthority(subscriberClient.rpc,subscriptionAuthorityPda,);if (!subscriptionAuthority.exists) {await subscriberClient.subscriptions.instructions.initSubscriptionAuthority({tokenMint,tokenProgram: TOKEN_PROGRAM_ADDRESS,userAta: subscriberAta,}).sendTransaction();}await subscriberClient.subscriptions.instructions.subscribe({merchant: merchantSigner.address,planId,tokenMint,}).sendTransaction();const [subscriptionPda] = await findSubscriptionDelegationPda({planPda,subscriber: subscriberSigner.address,});
Pobierz płatność
Sprzedawca lub autoryzowany puller podpisuje operację pobrania. Gdy plan
korzysta z listy dozwolonych miejsc docelowych, właściciel token account
odbiorcy musi być uwzględniony w destinations.
const receiverAta = address('MERCHANT_TOKEN_ACCOUNT_ADDRESS_HERE');await merchantClient.subscriptions.instructions.transferSubscription({caller: merchantOrPullerSigner,delegator: subscriberSigner.address,tokenMint,subscriptionPda,planPda,amount: 200_000n,receiverAta,tokenProgram: TOKEN_PROGRAM_ADDRESS,}).sendTransaction();
Anulowanie i Unieważnienie
Anulowanie oznacza subskrypcję jako kończącą się. Unieważnienie zamyka PDA subskrypcji po upływie terminu wygaśnięcia anulowania. Subskrybent podpisuje obie transakcje.
await subscriberClient.subscriptions.instructions.cancelSubscription({subscriber: subscriberSigner,planPda,subscriptionPda,}).sendTransaction();// Run this after the cancelled subscription's expiresAtTs has elapsed.await subscriberClient.subscriptions.instructions.revokeSubscription({authority: subscriberSigner,planPda,subscriptionPda,}).sendTransaction();
Anuluj Natychmiast
Gdy zarówno subskrybent, jak i aktualny właściciel planu podpiszą żądanie, cancelSubscriptionNow wygasa subskrypcję w momencie anulowania, zamiast na koniec okresu rozliczeniowego. Może również skrócić oczekujące anulowanie w okresie karencji. Zatwierdzenie jest powiązane z początkiem okresu zaobserwowanym w momencie podpisywania; jeśli subskrypcja zmieniła się od tego czasu, transakcja zostaje odrzucona (StaleSubscriptionApproval).
import { fetchSubscriptionDelegation } from '@solana/subscriptions';const subscription = await fetchSubscriptionDelegation(subscriberClient.rpc,subscriptionPda,);await subscriberClient.subscriptions.instructions.cancelSubscriptionNow({subscriber: subscriberSigner,merchant: merchantSigner,planPda,expectedCurrentPeriodStartTs: subscription.data.currentPeriodStartTs,}).sendTransaction();
Wznowienie Subskrypcji
Anulowaną subskrypcję można reaktywować przed jej unieważnieniem. Po
unieważnieniu konto subskrypcji zostaje zamknięte i subskrybent musi ponownie
subskrybować. Wznowienie wymaga od subskrybenta
SubscriptionAuthority dla mintowania planu, który program weryfikuje
(właściciel, mint oraz init_id) i odrzuca, jeśli jest nieaktualny lub ponownie
zainicjalizowany.
Instrukcja zawiera datę wygaśnięcia zaobserwowaną przez subskrybenta w momencie podpisywania (expectedExpiresAtTs); niezgodność skutkuje odrzuceniem (StaleSubscriptionApproval), więc nieaktualne podpisane wznowienie nie może usunąć późniejszego anulowania, którego subskrybent nigdy nie zatwierdził.
import { fetchSubscriptionDelegation } from '@solana/subscriptions';const subscription = await fetchSubscriptionDelegation(subscriberClient.rpc,subscriptionPda,);await subscriberClient.subscriptions.instructions.resumeSubscription({subscriber: subscriberSigner,planPda,tokenMint,expectedExpiresAtTs: subscription.data.expiresAtTs,}).sendTransaction();
Uwagi
amountjest w jednostkach bazowych. Dla tokena z 6 miejscami dziesiętnymi,5_000_000oznacza5tokenów.- TypeScript SDK pobiera aktualne warunki planu podczas
subscribe, gdy się je pominie. - Rust
SubscribeBuilderwymaga oczekiwanych warunków planu. Najpierw pobierz i zdekoduj konto planu, a następnie przekaż te pola przezSubscribeData. - Tylko merchant lub portfel wymieniony w
pullersmoże pobierać płatności. - Subskrybent podpisuje transakcje konfiguracji, anulowania i unieważnienia. Merchant lub zatwierdzony podmiot pobierający podpisuje transakcje pobierania płatności.
Is this page helpful?