Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recurring Memberships Implementation #19

Open
olasunkanmi-SE opened this issue Oct 14, 2024 · 0 comments
Open

Recurring Memberships Implementation #19

olasunkanmi-SE opened this issue Oct 14, 2024 · 0 comments

Comments

@olasunkanmi-SE
Copy link
Owner

olasunkanmi-SE commented Oct 14, 2024

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

  1. Membership Structure: We've added fields for next_payment_due, payment_interval, and is_recurring to support recurring payments.

  2. Create Recurring Membership: This function sets up the initial recurring membership, processes the first payment, and sets the next payment due date.

  3. 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.

  4. Cancel Recurring Membership: Allows users to stop their recurring membership.

  5. 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

  1. Indexer Service:

    • Listen to Solana blockchain events related to membership creation, updates, and payments.
    • Store membership data in a database for quick querying.
  2. 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

  1. Scheduler:

    • Regularly query the database for memberships with upcoming payments.
    • Initiate on-chain transactions for due payments.
  2. 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

  1. Notification Types:

    • Payment success confirmations
    • Upcoming payment reminders
    • Failed payment alerts
    • Membership expiration warnings
  2. 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

  1. Dashboard for Users:

    • View current memberships and their status
    • Cancel or modify recurring memberships
    • View payment history
  2. Dashboard for Creators:

    • Monitor active recurring memberships
    • View revenue from recurring payments
    • Adjust membership terms (with user consent)

3. Challenges and Considerations

  1. 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
  2. Failed Payments: Implement a retry mechanism with a maximum number of attempts before cancelling the recurring membership.

  3. Scalability: As the number of recurring memberships grows, ensure your off-chain services can scale accordingly. Consider using distributed systems for the payment processor.

  4. Security: Implement robust security measures for the off-chain services, especially for handling sensitive payment information.

  5. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant