PaiementsAbonnements

Plan d'abonnement

Un plan d'abonnement permet à un commerçant de publier des conditions de facturation que les utilisateurs peuvent accepter. Après qu'un utilisateur s'abonne, le commerçant ou un préleveur autorisé peut collecter jusqu'au montant du plan à chaque période de facturation.

Ce guide présente le flux complet sous forme de blocs de construction. Le commerçant crée un plan, l'abonné l'accepte, et le commerçant ou le préleveur collecte les paiements depuis le PDA d'abonnement résultant.

Installation

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

Créer un plan

Le commerçant est propriétaire du plan. Le PDA du plan est dérivé de l'adresse du commerçant et de planId.

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

Mettre à jour un plan

Le commerçant peut mettre à jour les champs modifiables du plan après sa création. Les abonnés existants conservent les conditions qu'ils ont acceptées, tandis que les nouveaux abonnés acceptent les conditions actuelles du plan.

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

S'abonner

L'abonné accepte les conditions actuelles du plan. Le PDA de l'abonnement est dérivé du PDA du plan et de l'adresse de l'abonné.

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

Encaisser un paiement

Le commerçant ou un extracteur figurant sur la liste blanche signe l'encaissement. Lorsque le plan utilise une liste blanche de destinations, le propriétaire du token account destinataire doit figurer dans 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();

Annuler et révoquer

L'annulation marque l'abonnement comme se terminant. La révocation ferme le PDA de l'abonnement après l'expiration du délai d'annulation. L'abonné signe les deux transactions.

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

Remarques

  • amount est en unités de base. Pour un jeton à 6 décimales, 5_000_000 signifie 5 jetons.
  • Le SDK TypeScript récupère les conditions du plan en direct lors de subscribe lorsque vous les omettez.
  • Le SubscribeBuilder Rust nécessite les conditions du plan attendues. Récupérez et décodez d'abord le compte du plan, puis transmettez ces champs via SubscribeData.
  • Seul le marchand ou un portefeuille répertorié dans pullers peut collecter les paiements.
  • L'abonné signe les transactions de configuration, d'annulation et de révocation. Le marchand ou le collecteur approuvé signe les transactions de collecte.

Is this page helpful?

Table des matières

Modifier la page
© 2026 Fondation Solana. Tous droits réservés.