Gói đăng ký cho phép người bán công bố các điều khoản thanh toán mà người dùng có thể chấp nhận. Sau khi người dùng đăng ký, người bán hoặc bên được ủy quyền có thể thu tiền lên đến số tiền trong gói cho mỗi kỳ thanh toán.
Hướng dẫn này trình bày toàn bộ quy trình dưới dạng các khối xây dựng. Người bán tạo gói, người đăng ký chấp nhận gói đó, và người bán hoặc bên được ủy quyền thu tiền từ PDA đăng ký được tạo ra.
Cài Đặt
pnpm add @solana/subscriptions @solana/kit @solana/kit-plugin-rpc @solana/kit-plugin-signer @solana-program/token
Tạo Gói
Người bán sở hữu gói. PDA của gói được tạo từ địa chỉ người bán và 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,});
Cập Nhật Gói
Người bán có thể cập nhật các trường có thể thay đổi của gói sau khi tạo. Những người đăng ký hiện tại vẫn giữ nguyên các điều khoản họ đã chấp nhận, trong khi người đăng ký mới sẽ chấp nhận các điều khoản hiện tại của gói.
Một số quy tắc áp dụng:
- Một
endTshữu hạn chỉ có thể được rút ngắn, không bao giờ được gia hạn hoặc xóa (PlanEndTsCannotExtend). - Trong gói
Sunset, bạn có thể xóa các puller (tập hợp mới phải là tập con của tập hợp hiện tại) để thu hồi quyền của một puller bị xâm phạm; trạng thái,endTsvà siêu dữ liệu vẫn bị đóng băng. - Các chỉnh sửa có hiệu lực trong kỳ thanh toán cuối cùng của gói miễn là
endTskhông thay đổi.
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();
Đăng Ký
Người đăng ký chấp nhận các điều khoản gói hiện tại. PDA đăng ký được suy ra từ PDA của gói và địa chỉ người đăng ký.
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,});
Thu Thanh Toán
Người bán hoặc một puller được đưa vào danh sách trắng sẽ ký xác nhận thu tiền.
Khi gói sử dụng danh sách cho phép đích, chủ sở hữu của token account nhận phải
được liệt kê trong 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();
Hủy Và Thu Hồi
Hủy đánh dấu gói đăng ký là sắp kết thúc. Thu hồi đóng PDA của gói đăng ký sau khi thời hạn hủy đã hết. Người đăng ký ký cả hai giao dịch.
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();
Tiếp Tục Gói Đăng Ký
Một gói đăng ký đã hủy có thể được kích hoạt lại trước khi bị thu hồi. Sau khi
bị thu hồi, tài khoản đăng ký sẽ bị đóng và người đăng ký phải
đăng ký lại. Tiếp tục yêu cầu SubscriptionAuthority của người
đăng ký cho mint của gói, mà chương trình xác thực (chủ sở hữu, mint và
init_id) và từ chối nếu đã cũ hoặc được khởi tạo lại.
await subscriberClient.subscriptions.instructions.resumeSubscription({subscriber: subscriberSigner,planPda,tokenMint,}).sendTransaction();
Lưu Ý
amounttính theo đơn vị cơ sở. Với token có 6 chữ số thập phân,5_000_000có nghĩa là5token.- TypeScript SDK lấy các điều khoản gói trực tiếp trong quá trình
subscribekhi bạn bỏ qua chúng. SubscribeBuildercủa Rust cần các điều khoản gói dự kiến. Tải và giải mã tài khoản gói trước, sau đó truyền các trường đó quaSubscribeData.- Chỉ merchant hoặc ví được liệt kê trong
pullersmới có thể thu thanh toán. - Người đăng ký ký các giao dịch thiết lập, hủy và thu hồi. Merchant hoặc puller được phê duyệt ký các giao dịch thu tiền.
Is this page helpful?