-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Recurring Memberships Implementation
1. On-chain Implementation
1.1 Membership Program Updates
We'll need to update our Membership Program to handle recurring payments. Here's an expanded version of the Membership
struct and related functions:
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount};
#[account]
pub struct Membership {
pub owner: Pubkey,
pub membership_type: Pubkey,
pub expiration: i64,
pub next_payment_due: i64,
pub payment_interval: i64,
pub amount: u64,
pub is_recurring: bool,
pub cancellation_fee: u64
}
#[derive(Accounts)]
pub struct ProcessRecurringPayment<'info> {
#[account(mut)]
pub membership: Account<'info, Membership>,
#[account(mut)]
pub user_token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub protocol_token_account: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>,
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[program]
pub mod membership_program {
use super::*;
pub fn create_recurring_membership(
ctx: Context<CreateRecurringMembership>,
payment_interval: i64,
amount: u64
) -> Result<()> {
let membership = &mut ctx.accounts.membership;
let clock = Clock::get()?;
membership.owner = ctx.accounts.user.key();
membership.expiration = clock.unix_timestamp + payment_interval;
membership.next_payment_due = clock.unix_timestamp + payment_interval;
membership.payment_interval = payment_interval;
membership.amount = amount;
membership.is_recurring = true;
// Process initial payment
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
token::Transfer {
from: ctx.accounts.user_token_account.to_account_info(),
to: ctx.accounts.protocol_token_account.to_account_info(),
authority: ctx.accounts.user.to_account_info(),
},
),
amount,
)?;
Ok(())
}
pub fn process_recurring_payment(ctx: Context<ProcessRecurringPayment>) -> Result<()> {
let membership = &mut ctx.accounts.membership;
let clock = Clock::get()?;
if clock.unix_timestamp >= membership.next_payment_due {
// Process payment
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
token::Transfer {
from: ctx.accounts.user_token_account.to_account_info(),
to: ctx.accounts.protocol_token_account.to_account_info(),
authority: ctx.accounts.user.to_account_info(),
},
),
membership.amount,
)?;
// Update next payment due and expiration
membership.next_payment_due += membership.payment_interval;
membership.expiration = membership.next_payment_due;
Ok(())
} else {
Err(ProgramError::PaymentNotDue.into())
}
}
pub fn cancel_recurring_membership(ctx: Context<CancelRecurringMembership>) -> Result<()> {
let membership = &mut ctx.accounts.membership;
membership.is_recurring = false;
Ok(())
}
}
1.2 Key Aspects of On-chain Implementation
-
Membership Structure: We've added fields for
next_payment_due
,payment_interval
, andis_recurring
to support recurring payments. -
Create Recurring Membership: This function sets up the initial recurring membership, processes the first payment, and sets the next payment due date.
-
Process Recurring Payment: This function checks if a payment is due, processes the payment if it is, and updates the next payment due date and expiration.
-
Cancel Recurring Membership: Allows users to stop their recurring membership.
-
Time-based Logic: We use Solana's
Clock
sysvar to get the current timestamp for comparisons and updates.
2. Off-chain Implementation
The off-chain component is crucial for managing recurring payments efficiently. Here's how we can implement it:
2.1 Indexer and Database
-
Indexer Service:
- Listen to Solana blockchain events related to membership creation, updates, and payments.
- Store membership data in a database for quick querying.
-
Database Schema:
CREATE TABLE recurring_memberships (
membership_address VARCHAR(44) PRIMARY KEY,
owner_address VARCHAR(44) NOT NULL,
next_payment_due BIGINT NOT NULL,
payment_interval BIGINT NOT NULL,
amount BIGINT NOT NULL,
is_active BOOLEAN NOT NULL
);
2.2 Payment Processor Service
-
Scheduler:
- Regularly query the database for memberships with upcoming payments.
- Initiate on-chain transactions for due payments.
-
Implementation Pseudocode:
def process_recurring_payments():
current_time = get_current_unix_timestamp()
due_memberships = db.query("""
SELECT * FROM recurring_memberships
WHERE is_active = TRUE AND next_payment_due <= ?
""", (current_time,))
for membership in due_memberships:
try:
# Initiate on-chain transaction
tx = send_process_recurring_payment_transaction(membership.membership_address)
if tx.is_successful:
# Update local database
db.execute("""
UPDATE recurring_memberships
SET next_payment_due = next_payment_due + payment_interval
WHERE membership_address = ?
""", (membership.membership_address,))
except InsufficientFundsError:
notify_user(membership.owner_address, "Insufficient funds for recurring payment")
except Exception as e:
log_error(f"Error processing payment for {membership.membership_address}: {str(e)}")
# Run this function periodically, e.g., every hour
schedule.every(1).hour.do(process_recurring_payments)
2.3 User Notification Service
-
Notification Types:
- Payment success confirmations
- Upcoming payment reminders
- Failed payment alerts
- Membership expiration warnings
-
Implementation:
- Use email, push notifications, or on-platform messaging.
- Integrate with the Payment Processor Service to trigger notifications based on payment events.
2.4 User Interface
-
Dashboard for Users:
- View current memberships and their status
- Cancel or modify recurring memberships
- View payment history
-
Dashboard for Creators:
- Monitor active recurring memberships
- View revenue from recurring payments
- Adjust membership terms (with user consent)
3. Challenges and Considerations
-
Transaction Fees: Consider how to handle Solana transaction fees for recurring payments. Options include:
- Deducting fees from the payment amount
- Charging a slightly higher amount to cover fees
- Subsidizing fees for users
-
Failed Payments: Implement a retry mechanism with a maximum number of attempts before cancelling the recurring membership.
-
Scalability: As the number of recurring memberships grows, ensure your off-chain services can scale accordingly. Consider using distributed systems for the payment processor.
-
Security: Implement robust security measures for the off-chain services, especially for handling sensitive payment information.
-
Compliance: Ensure the system complies with relevant financial regulations, especially if dealing with fiat currency on-ramps.
By combining these on-chain and off-chain components, you can create a robust system for managing recurring memberships in your Solana-based protocol. This approach leverages Solana's fast and inexpensive transactions while using off-chain services to manage the complexities of recurring payments efficiently.