ZahlungenAbonnements

Abonnementplan

Ein Abonnementplan ermöglicht es einem Händler, Abrechnungsbedingungen zu veröffentlichen, die Nutzer akzeptieren können. Nachdem ein Nutzer abonniert hat, kann der Händler oder ein autorisierter Einzieher bis zum Planbetrag in jeder Abrechnungsperiode einziehen.

Diese Anleitung zeigt den vollständigen Ablauf als Bausteine. Der Händler erstellt einen Plan, der Abonnent akzeptiert ihn, und der Händler oder Einzieher zieht Zahlungen aus dem resultierenden Abonnement-PDA ein.

Installation

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

Plan erstellen

Der Händler ist Eigentümer des Plans. Der Plan-PDA wird von der Händleradresse und planId abgeleitet.

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

Plan aktualisieren

Der Händler kann veränderbare Planfelder nach der Erstellung aktualisieren. Bestehende Abonnenten behalten die von ihnen akzeptierten Bedingungen bei, während neue Abonnenten die aktuellen Planbedingungen akzeptieren.

Es gelten einige Regeln:

  • Eine endliche endTs kann nur verkürzt, niemals verlängert oder gelöscht werden (PlanEndTsCannotExtend).
  • Bei einem Sunset-Plan können Sie Puller entfernen (die neue Gruppe muss eine Teilmenge der aktuellen sein), um einen kompromittierten Puller zu sperren; Status, endTs und Metadaten bleiben eingefroren.
  • Änderungen sind während der letzten Abrechnungsperiode eines Plans möglich, solange endTs unverändert bleibt.
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();

Abonnieren

Der Abonnent akzeptiert die aktuellen Planbedingungen. Die Abonnement-PDA wird aus der Plan-PDA und der Abonnentenadresse abgeleitet.

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

Eine Zahlung einziehen

Der Händler oder ein autorisierter Puller unterzeichnet den Einzug. Wenn der Plan eine Ziel-Zulassungsliste verwendet, muss der Inhaber des Empfänger-token account in destinations aufgeführt sein.

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

Abbrechen und Widerrufen

Das Abbrechen markiert das Abonnement als auslaufend. Das Widerrufen schließt das Abonnement-PDA, nachdem die Ablaufzeit der Kündigung verstrichen ist. Der Abonnent signiert beide Transaktionen.

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

Ein Abonnement Fortsetzen

Ein gekündigtes Abonnement kann reaktiviert werden, bevor es widerrufen wird. Nach dem Widerruf wird das Abonnement-Konten geschlossen und der Abonnent muss erneut abonnieren. Für die Fortsetzung benötigt der Abonnent SubscriptionAuthority für die Mint des Plans, den das Programm validiert (Inhaber, Mint und init_id) und ablehnt, wenn er veraltet oder neu initialisiert ist.

await subscriberClient.subscriptions.instructions
.resumeSubscription({
subscriber: subscriberSigner,
planPda,
tokenMint,
})
.sendTransaction();

Hinweise

  • amount ist in Basiseinheiten. Bei einem Token mit 6 Dezimalstellen bedeutet 5_000_000 5 Token.
  • Das TypeScript-SDK ruft aktuelle Planbedingungen während subscribe ab, wenn diese weggelassen werden.
  • Das Rust-SubscribeBuilder benötigt die erwarteten Planbedingungen. Rufen Sie zunächst das Plan-Konten ab und dekodieren Sie es, und übergeben Sie dann diese Felder über SubscribeData.
  • Nur der Händler oder eine in pullers aufgeführte Wallet kann Zahlungen einziehen.
  • Der Abonnent signiert Einrichtungs-, Abbruch- und Widerrufstransaktionen. Der Händler oder ein autorisierter Einzieher signiert Einzugstransaktionen.

Is this page helpful?

Inhaltsverzeichnis

Seite bearbeiten
© 2026 Solana Foundation. Alle Rechte vorbehalten.