Tilaussuunnitelma

Tilaussuunnitelma mahdollistaa kauppiaan julkaista laskutusehdot, jotka käyttäjät voivat hyväksyä. Kun käyttäjä tilaa, kauppias tai hyväksytty noutaja voi periä enintään suunnitelman summan jokaiselta laskutusjaksolta.

Tämä opas esittää koko työnkulun rakennuspalikoina. Kauppias luo suunnitelman, tilaaja hyväksyy sen, ja kauppias tai noutaja kerää maksut tuloksena syntyneestä tilaus-PDA:sta.

Asennus

pnpm add @solana/subscriptions @solana/kit @solana/kit-plugin-rpc @solana/kit-plugin-signer @solana-program/token

Suunnitelman luominen

Kauppias omistaa suunnitelman. Suunnitelma-PDA johdetaan kauppiaan osoitteesta ja planId.

Sponsori voi rahoittaa suunnitelman rent-maksun välittämällä valinnaisen payer-parametrin, jolloin kauppias säilyy suunnitelman omistajana. Suunnitelman poistaminen palauttaa maksun omistajalle, ei maksajalle, joten sponsorointi tulee rajoittaa ketjun ulkopuolisesti.

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,
});

Suunnitelman päivittäminen

Kauppias voi päivittää muokattavat suunnitelmakentät luomisen jälkeen. Olemassa olevat tilaajat pitävät hyväksymänsä ehdot, kun taas uudet tilaajat hyväksyvät nykyiset suunnitelman ehdot.

Muutama sääntö pätee:

  • Rajallista endTs voidaan vain lyhentää, ei koskaan pidentää tai tyhjentää (PlanEndTsCannotExtend).
  • Sunset-suunnitelmassa voit poistaa vetäjiä (uuden joukon on oltava nykyisen osajoukko) vaarantuneen vetäjän oikeuksien peruuttamiseksi; tila, endTs ja metatiedot pysyvät muuttumattomina.
  • Muokkaukset toimivat suunnitelman viimeisen laskutuskauden aikana, kunhan endTs pysyy muuttumattomana.
  • Instruktiossa on allekirjoitushetkellä havaittu suunnitelman tila (expectedCreatedAt, expectedEndTs, expectedPullers, expectedMetadataUri). Jos live-suunnitelma ei enää vastaa tätä tilaa, päivitys hylätään (StalePlanApproval), joten vanhentunut allekirjoitettu päivitys ei voi palauttaa poistettuja pullereita tai kumota myöhempiä muokkauksia. Liitännäisasiakkaan updatePlan hakee live-tilan puolestasi; kun rakennat manuaalisesti, hae suunnitelma ja välitä kentät itse.
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();

Tilaa

Tilaaja hyväksyy voimassa olevat suunnitelmaehdot. Tilauksen PDA johdetaan suunnitelman PDA:sta ja tilaajan osoitteesta.

Yhdistääksesi auktoriteetin alustuksen ja tilauksen yhdeksi transaktioksi, välitä sentineliarvo UNKNOWN_INIT_ID (viety TypeScript SDK:sta) expectedSubscriptionAuthorityInitId-parametrina. Ohjelma hyväksyy auktoriteetin vain, jos se luotiin nykyisessä slot-yksikössä, joten sentineliarvo toimii uusille rekisteröitymisille; palaavan käyttäjän, jonka auktoriteetti luotiin aiemmassa slot-yksikössä, on välitettävä todellinen initId (liitännäisasiakas hakee sen puolestasi) tai kutsu epäonnistuu virheellä 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,
});

Kerää Maksu

Kauppias tai sallittu vetäjä allekirjoittaa keräyksen. Kun suunnitelma käyttää kohteen sallittujen listaa, vastaanottajan token account -omistajan on oltava merkittynä destinations:ssä.

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();

Peruuta ja kumoa

Peruuttaminen merkitsee tilauksen päättyväksi. Kumoaminen sulkee tilauksen PDA:n, kun peruutuksen voimassaoloaika on kulunut. Tilaaja allekirjoittaa molemmat tapahtumat.

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();

Peruuta välittömästi

Kun sekä tilaaja että nykyinen suunnitelman omistaja allekirjoittavat, cancelSubscriptionNow asettaa tilauksen päättymään peruutushetkellä laskutuskauden loppuun sijasta. Se voi myös lyhentää odottavaa lisäaikakauden peruutusta. Hyväksyntä on sidottu allekirjoitushetkellä havaittuun kauden alkuun; jos tilaus on muuttunut sen jälkeen, transaktio hylätään (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();

Jatka tilausta

Peruutetun tilauksen voi reaktivoida ennen sen kumoamista. Kun tilaus on kumottu, tilaustilin tiedot suljetaan ja tilaajan täytyy tilata uudelleen. Jatkaminen edellyttää tilaajan SubscriptionAuthority suunnitelman minttiä varten, jonka ohjelma vahvistaa (omistaja, mintti ja init_id) ja hylkää, jos se on vanhentunut tai uudelleen alustettu.

Instruktiossa on tilaajan allekirjoitushetkellä havaitsema vanhentumisaika (expectedExpiresAtTs); ristiriita hylätään (StaleSubscriptionApproval), joten vanhentunut allekirjoitettu jatkaminen ei voi poistaa myöhempää peruutusta, jota tilaaja ei ole hyväksynyt.

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();

Huomioita

  • amount on perusyksiköissä. 6 desimaalin tokenille 5_000_000 tarkoittaa 5 tokenia.
  • TypeScript SDK hakee reaaliaikaiset suunnitelman ehdot subscribe aikana, kun jätät ne pois.
  • Rust SubscribeBuilder tarvitsee odotetut suunnitelman ehdot. Hae ja pura suunnitelmatili ensin, sitten välitä nämä kentät SubscribeData kautta.
  • Vain kauppias tai pullers-listalla oleva lompakko voi kerätä maksuja.
  • Tilaaja allekirjoittaa asennuksen, peruutuksen ja kumoamisen tapahtumat. Kauppias tai hyväksytty kerääjä allekirjoittaa maksutapahtumat.

Is this page helpful?

Sisällysluettelo

Muokkaa sivua
© 2026 Solana Foundation. Kaikki oikeudet pidätetään.