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.

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 actuales del plan. 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 extractor autorizado firma la cobranza. Cuando el plan utiliza una lista de destinos permitidos, el propietario de la cuenta de tokens receptora 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 el PDA de suscripción después de que haya transcurrido el período 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();

Notas

  • amount está en unidades base. Para un token de 6 decimales, 5_000_000 significa 5 tokens.
  • El SDK de TypeScript obtiene los términos del plan en tiempo real durante subscribe cuando los omites.
  • El SubscribeBuilder de Rust necesita los términos del plan esperados. Primero obtén y decodifica la cuenta del plan, luego pasa esos campos a través de SubscribeData.
  • Solo el comerciante o una billetera listada en pullers puede cobrar pagos.
  • El suscriptor firma las transacciones de configuración, cancelación y revocación. El comerciante o el extractor aprobado firma las transacciones de cobro.

Is this page helpful?

Tabla de Contenidos

Editar Página