المدفوعاتالاشتراكات

خطة الاشتراك

تتيح خطة الاشتراك للتاجر نشر شروط الفوترة التي يمكن للمستخدمين قبولها. بعد اشتراك المستخدم، يمكن للتاجر أو السّاحب المُعتمد تحصيل ما يصل إلى مبلغ الخطة في كل فترة فوترة.

يعرض هذا الدليل التدفق الكامل كوحدات بناء. ينشئ التاجر خطة، ويقبلها المشترك، ثم يقوم التاجر أو السّاحب بتحصيل المدفوعات من حساب PDA الناتج عن الاشتراك.

التثبيت

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

إنشاء خطة

يمتلك التاجر الخطة. يُشتق حساب PDA الخاص بالخطة من عنوان التاجر و 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,
});

تحديث الخطة

يمكن للتاجر تحديث حقول الخطة القابلة للتعديل بعد الإنشاء. يحتفظ المشتركون الحاليون بالشروط التي قبلوها، بينما يقبل المشتركون الجدد شروط الخطة الحالية.

تنطبق بعض القواعد:

  • يمكن تقصير endTs المحدود فقط، ولا يمكن تمديده أو مسحه أبدًا (PlanEndTsCannotExtend).
  • في خطة Sunset يمكنك إزالة المسحوبات (يجب أن تكون المجموعة الجديدة مجموعة فرعية من المجموعة الحالية) لإلغاء صلاحية ساحب مخترق؛ تبقى الحالة وendTs والبيانات الوصفية مجمّدة.
  • تعمل التعديلات خلال فترة الفوترة الأخيرة للخطة طالما ظل endTs دون تغيير.
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();

الاشتراك

يقبل المشترك شروط الخطة الحالية. يتم اشتقاق PDA الخاص بالاشتراك من PDA الخطة وعنوان المشترك.

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

تحصيل دفعة

يوقّع التاجر أو أحد المسحوبات المدرجة في القائمة البيضاء على عملية التحصيل. عندما تستخدم الخطة قائمة مسموح بها للوجهات، يجب إدراج مالك token account المستلم في 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();

الإلغاء والسحب

يُعلِّم الإلغاءُ الاشتراكَ بأنه سينتهي. أما السحب فيُغلق حساب PDA الخاص بالاشتراك بعد انقضاء مدة انتهاء الإلغاء. يوقّع المشترك كلا المعاملتين.

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

استئناف الاشتراك

يمكن إعادة تفعيل الاشتراك الملغى قبل سحبه. وبمجرد سحبه، يُغلق حساب الاشتراك ويتعين على المشترك الاشتراك من جديد. يستلزم الاستئناف امتلاك المشترك SubscriptionAuthority الخاص بالمنت المرتبط بالخطة، والذي يتحقق منه البرنامج (المالك، والمنت، وinit_id) ويرفضه إذا كان قديمًا أو مُعاد تهيئته.

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

ملاحظات

  • amount بالوحدات الأساسية. بالنسبة للرمز ذي 6 خانات عشرية، يعني 5_000_000 قيمة 5 رمزًا.
  • يجلب TypeScript SDK شروط الخطة المباشرة أثناء subscribe عند حذفها.
  • يحتاج SubscribeBuilder في Rust إلى شروط الخطة المتوقعة. احرص على جلب حساب الخطة وفك ترميزه أولًا، ثم مرِّر تلك الحقول عبر SubscribeData.
  • يمكن للتاجر فقط أو أي محفظة مدرجة في pullers تحصيل المدفوعات.
  • يوقّع المشترك معاملات الإعداد والإلغاء والسحب. أما التاجر أو المحصّل المعتمد فيوقّع معاملات التحصيل.

Is this page helpful?

جدول المحتويات

تعديل الصفحة