PagamentosAssinaturas

Plano de Assinatura

Um plano de assinatura permite que um comerciante publique termos de cobrança que os usuários podem aceitar. Após um usuário assinar, o comerciante ou um cobrador aprovado pode coletar até o valor do plano a cada período de cobrança.

Este guia mostra o fluxo completo como blocos de construção. O comerciante cria um plano, o assinante o aceita, e o comerciante ou cobrador coleta pagamentos do PDA de assinatura resultante.

Instalar

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

Criar um Plano

O comerciante é o proprietário do plano. O PDA do plano é derivado do endereço do comerciante e 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,
});

Atualizar um Plano

O comerciante pode atualizar campos mutáveis do plano após a criação. Assinantes existentes mantêm os termos que aceitaram, enquanto novos assinantes aceitam os termos atuais do plano.

Algumas regras se aplicam:

  • Um endTs finito só pode ser encurtado, nunca estendido ou removido (PlanEndTsCannotExtend).
  • Em um plano Sunset você pode remover pullers (o novo conjunto deve ser um subconjunto do atual) para revogar um puller comprometido; o status, endTs e os metadados permanecem congelados.
  • As edições funcionam durante o período de cobrança final de um plano, desde que o endTs não seja alterado.
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();

Assinar

O assinante aceita os termos do plano atual. O PDA de assinatura é derivado do PDA do plano e do endereço do assinante.

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

Coletar um Pagamento

O comerciante ou um puller autorizado assina a cobrança. Quando o plano utiliza uma lista de destinos permitidos, o proprietário do token account receptor deve estar listado em 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();

Cancelar e Revogar

Cancelar marca a subscrição como encerrando. Revogar fecha a PDA da subscrição após o prazo de expiração do cancelamento ter decorrido. O subscritor assina ambas as transações.

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

Retomar uma Subscrição

Uma subscrição cancelada pode ser reativada antes de ser revogada. Uma vez revogada, a conta de subscrição é encerrada e o subscritor deve subscrever novamente. Retomar requer o SubscriptionAuthority do subscritor para o mint do plano, que o programa valida (proprietário, mint e init_id) e rejeita se estiver desatualizado ou reinicializado.

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

Notas

  • amount está em unidades base. Para um token de 6 decimais, 5_000_000 significa 5 tokens.
  • O SDK TypeScript obtém os termos do plano em tempo real durante subscribe quando os omite.
  • O SubscribeBuilder em Rust necessita dos termos esperados do plano. Obtenha e descodifique a conta do plano primeiro, em seguida passe esses campos através de SubscribeData.
  • Apenas o comerciante ou uma carteira listada em pullers pode cobrar pagamentos.
  • O subscritor assina as transações de configuração, cancelamento e revogação. O comerciante ou o cobrador aprovado assina as transações de cobrança.

Is this page helpful?

Índice

Editar Página
© 2026 Fundação Solana. Todos os direitos reservados.