Paket langganan memungkinkan pedagang menerbitkan ketentuan penagihan yang dapat diterima oleh pengguna. Setelah pengguna berlangganan, pedagang atau penarik yang disetujui dapat mengumpulkan hingga jumlah paket setiap periode penagihan.
Panduan ini menunjukkan alur lengkap sebagai blok bangunan. Pedagang membuat paket, pelanggan menerimanya, dan pedagang atau penarik mengumpulkan pembayaran dari PDA langganan yang dihasilkan.
Instalasi
pnpm add @solana/subscriptions @solana/kit @solana/kit-plugin-rpc @solana/kit-plugin-signer @solana-program/token
Membuat Paket
Pedagang memiliki paket tersebut. PDA paket diturunkan dari alamat pedagang dan
planId.
Sponsor dapat mendanai rent plan dengan meneruskan payer opsional sementara
pedagang tetap menjadi pemilik plan. Menghapus plan akan mengembalikan dana kepada pemilik, bukan
pembayar, jadi kelola sponsorship di luar rantai (off-chain).
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,});
Memperbarui Paket
Pedagang dapat memperbarui kolom paket yang dapat diubah setelah pembuatan. Pelanggan yang sudah ada tetap mempertahankan ketentuan yang telah mereka terima, sementara pelanggan baru menerima ketentuan paket saat ini.
Beberapa aturan berlaku:
endTsyang terbatas hanya dapat dipersingkat, tidak pernah diperpanjang atau dihapus (PlanEndTsCannotExtend).- Pada paket
Sunset, Anda dapat menghapus puller (set baru harus merupakan subset dari yang saat ini ada) untuk mencabut puller yang telah dikompromikan; status,endTs, dan metadata tetap dibekukan. - Pengeditan berfungsi selama periode penagihan terakhir suatu paket selama
endTstidak berubah. - Instruksi membawa status plan yang diamati saat penandatanganan
(
expectedCreatedAt,expectedEndTs,expectedPullers,expectedMetadataUri). Jika plan yang aktif tidak lagi cocok, pembaruan akan ditolak (StalePlanApproval), sehingga pembaruan yang sudah ditandatangani secara usang tidak dapat memulihkan puller yang dihapus atau mengembalikan pengeditan sebelumnya.updatePlanmilik klien plugin mengambil status terbaru untuk Anda; saat membangun secara manual, ambil plan dan teruskan field-nya sendiri.
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();
Berlangganan
Pelanggan menerima ketentuan paket saat ini. PDA langganan diturunkan dari PDA paket dan alamat pelanggan.
Untuk menggabungkan inisialisasi otoritas dan proses subscribe ke dalam satu transaksi, teruskan
sentinel UNKNOWN_INIT_ID (diekspor oleh TypeScript SDK) sebagai
expectedSubscriptionAuthorityInitId. Program menerima otoritas hanya jika
otoritas tersebut dibuat dalam slot saat ini, sehingga sentinel berfungsi untuk pendaftaran baru; pengguna
yang kembali dan otoritasnya dibuat pada slot sebelumnya harus meneruskan
initId yang sebenarnya (klien plugin mengambilnya untuk Anda) atau panggilan akan gagal dengan
StaleSubscriptionAuthority.
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,});
Kumpulkan Pembayaran
Merchant atau puller yang masuk daftar putih menandatangani pengumpulan. Ketika
paket menggunakan daftar izin tujuan, pemilik token account penerima harus
tercantum dalam 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();
Batalkan Dan Cabut
Pembatalan menandai langganan sebagai akan berakhir. Pencabutan menutup PDA langganan setelah masa berlaku pembatalan habis. Pelanggan menandatangani kedua transaksi tersebut.
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();
Batalkan Segera
Ketika subscriber dan pemilik plan saat ini sama-sama menandatangani,
cancelSubscriptionNow mengakhiri langganan pada waktu pembatalan alih-alih
pada akhir periode penagihan. Instruksi ini juga dapat mempersingkat pembatalan masa tenggang yang tertunda. Persetujuan terikat pada awal periode yang diamati saat penandatanganan; jika
langganan telah berubah sejak saat itu, transaksi akan ditolak
(StaleSubscriptionApproval).
import { fetchSubscriptionDelegation } from '@solana/subscriptions';const subscription = await fetchSubscriptionDelegation(subscriberClient.rpc,subscriptionPda,);await subscriberClient.subscriptions.instructions.cancelSubscriptionNow({subscriber: subscriberSigner,merchant: merchantSigner,planPda,expectedCurrentPeriodStartTs: subscription.data.currentPeriodStartTs,}).sendTransaction();
Lanjutkan Langganan
Langganan yang dibatalkan dapat diaktifkan kembali sebelum dicabut. Setelah
dicabut, akun langganan ditutup dan pelanggan harus berlangganan
kembali. Melanjutkan memerlukan SubscriptionAuthority milik pelanggan untuk
mint paket, yang divalidasi oleh program (pemilik, mint, dan init_id) dan
ditolak jika sudah kedaluwarsa atau diinisialisasi ulang.
Instruksi membawa waktu kedaluwarsa yang diamati subscriber saat penandatanganan
(expectedExpiresAtTs); ketidakcocokan akan ditolak (StaleSubscriptionApproval),
sehingga resume yang sudah ditandatangani secara usang tidak dapat menghapus pembatalan yang dilakukan belakangan dan tidak pernah disetujui oleh subscriber.
import { fetchSubscriptionDelegation } from '@solana/subscriptions';const subscription = await fetchSubscriptionDelegation(subscriberClient.rpc,subscriptionPda,);await subscriberClient.subscriptions.instructions.resumeSubscription({subscriber: subscriberSigner,planPda,tokenMint,expectedExpiresAtTs: subscription.data.expiresAtTs,}).sendTransaction();
Catatan
amountdalam satuan dasar. Untuk token 6 desimal,5_000_000berarti5token.- TypeScript SDK mengambil ketentuan paket secara langsung selama
subscribeapabila Anda tidak menyertakannya. SubscribeBuilderRust memerlukan ketentuan paket yang diharapkan. Ambil dan dekode akun paket terlebih dahulu, lalu teruskan kolom-kolom tersebut melaluiSubscribeData.- Hanya merchant atau dompet yang terdaftar di
pullersyang dapat mengumpulkan pembayaran. - Pelanggan menandatangani transaksi pengaturan, pembatalan, dan pencabutan. Merchant atau puller yang disetujui menandatangani transaksi pengumpulan.
Is this page helpful?