PagosSuscripciones

Plan de Suscripción

Un plan de suscripción permite a un comerciante publicar términos de facturación que los usuarios pueden aceptar. Después de que un usuario se suscribe, el comerciante o un extractor aprobado puede cobrar hasta el monto del plan en cada período de facturación.

Esta guía muestra el flujo completo como bloques de construcción. El comerciante crea un plan, el suscriptor lo acepta, y el comerciante o extractor cobra los pagos desde el PDA de suscripción resultante.

Instalación

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

Crear un Plan

El comerciante es propietario del plan. El PDA del plan se deriva de la dirección del comerciante y 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,
});

Actualizar un Plan

El comerciante puede actualizar los campos mutables del plan después de su creación. Los suscriptores existentes mantienen los términos que aceptaron, mientras que los nuevos suscriptores aceptan los términos actuales del plan.

Se aplican algunas reglas:

  • Un endTs finito solo puede acortarse, nunca extenderse ni borrarse (PlanEndTsCannotExtend).
  • En un plan Sunset puedes eliminar pullers (el nuevo conjunto debe ser un subconjunto del actual) para revocar un puller comprometido; el estado, endTs y los metadatos permanecen congelados.
  • Las ediciones funcionan durante el último período de facturación de un plan siempre que endTs no cambie.
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();

Suscribirse

El suscriptor acepta los términos del plan vigente. El PDA de suscripción se deriva del PDA del plan y la dirección del suscriptor.

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

Cobrar un Pago

El comerciante o un puller incluido en la lista blanca firma la cobranza. Cuando el plan utiliza una lista de destinos permitidos, el propietario del token account receptor debe estar incluido en 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 y Revocar

Cancelar marca la suscripción como finalizada. Revocar cierra la PDA de la suscripción una vez transcurrido el plazo de vencimiento de la cancelación. El suscriptor firma ambas transacciones.

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

Reanudar una Suscripción

Una suscripción cancelada puede reactivarse antes de ser revocada. Una vez revocada, la cuenta de suscripción se cierra y el suscriptor debe suscribirse de nuevo. Para reanudar, se requiere la SubscriptionAuthority del suscriptor para el mint del plan, que el programa valida (propietario, mint e init_id) y rechaza si está desactualizada o ha sido reinicializada.

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

Notas

  • amount está en unidades base. Para un token de 6 decimales, 5_000_000 equivale a 5 tokens.
  • El SDK de TypeScript obtiene los términos del plan en tiempo real durante subscribe si no se especifican.
  • El SubscribeBuilder de Rust requiere los términos esperados del plan. Obtén y decodifica primero la cuenta del plan, luego pasa esos campos a través de SubscribeData.
  • Solo el comerciante o una billetera incluida en pullers puede cobrar pagos.
  • El suscriptor firma las transacciones de configuración, cancelación y revocación. El comerciante o el extractor autorizado firma las transacciones de cobro.

Is this page helpful?

Tabla de Contenidos

Editar Página