FX Memberships
Simple membership plugin with Stripe and PayPal integration using hosted checkout portals.
by Ciprian Popescu · github.com/wolffe/fx-memberships · website
★ 0stars
0forks
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/wolffe/fx-memberships/archive/refs/heads/master.zipReadme
FX Memberships Plugin
Overview
Name: FX Memberships
Version: 1.0.1
Minimum ClassicPress: 2.5.0
Minimum PHP: 8.5
Architecture: Functional programming (no classes), PHP-first approach
Technical Requirements
- Functional programming only (no classes)
- Vanilla JavaScript only (no React, TypeScript, build processes)
- No Composer - Stripe PHP library included manually
- Full
<?phptags (no short tags) - Curly braces for control structures (no endif, endforeach)
- Strict comparison (
===and!==) - DRY principles
- ClassicPress 2.5+ compatible (uses ClassicPress-compatible core APIs)
Architecture
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Your Site │────▶│ Stripe/PayPal │────▶│ Their Portal │
│ (verify only) │◀────│ (webhooks) │ │ (manage billing)│
└─────────────────┘ └──────────────────┘ └─────────────────┘
Key Principle: Offload billing management to Stripe/PayPal hosted portals. We only:
- Generate checkout links
- Receive webhooks for status updates
- Store subscription status in user meta
- Restrict content based on status
File Structure
fx-memberships/
├── fx-memberships.php # Main plugin file
├── uninstall.php # Clean uninstall handler
├── readme.txt # Plugin readme
│
├── includes/
│ ├── core.php # Core functions, activation/deactivation
│ ├── user-meta.php # User subscription meta functions
│ ├── content-restriction.php # Content restriction logic & shortcodes
│ ├── helpers.php # Utility/helper functions
│ │
│ ├── stripe/
│ │ ├── stripe-checkout.php # Stripe Checkout link generation
│ │ ├── stripe-webhooks.php # Stripe webhook handler
│ │ ├── stripe-api.php # Stripe API calls (status checks)
│ │ └── stripe-portal.php # Customer portal redirect
│ │
│ └── paypal/
│ ├── paypal-checkout.php # PayPal Subscribe button generation
│ ├── paypal-webhooks.php # PayPal webhook handler
│ ├── paypal-api.php # PayPal API calls (status checks)
│ └── paypal-portal.php # PayPal billing portal redirect
│
├── admin/
│ ├── admin-menu.php # Admin menu registration
│ ├── settings-page.php # Plugin settings page
│ ├── subscribers-page.php # Subscriber management page
│ ├── membership-levels.php # Membership level configuration
│ └── admin-ajax.php # Admin AJAX handlers
│
├── assets/
│ ├── css/
│ │ ├── admin.css # Admin styles
│ │ └── frontend.css # Frontend styles
│ └── js/
│ ├── admin.js # Admin scripts (vanilla JS)
│ └── frontend.js # Frontend scripts (vanilla JS)
│
└── vendor/
└── stripe-php/ # Stripe PHP library (manual include)
Database Schema
User Meta Keys
| Meta Key | Description | Example Value |
|---|---|---|
_fxm_subscription_id |
Stripe/PayPal subscription ID | sub_1234567890 |
_fxm_subscription_status |
Current status | active, canceled, past_due, paused |
_fxm_subscription_provider |
Payment provider | stripe or paypal |
_fxm_customer_id |
Provider customer ID | cus_1234567890 |
_fxm_membership_level |
Current membership level slug | premium, basic |
_fxm_subscription_start |
Subscription start timestamp | 1702147200 |
_fxm_subscription_end |
Current period end timestamp | 1704825600 |
_fxm_subscription_updated |
Last update timestamp | 1702147200 |
Options (wp_options)
| Option Key | Description |
|---|---|
fxm_settings |
Main plugin settings (serialized array) |
fxm_membership_levels |
Defined membership levels (serialized array) |
fxm_version |
Installed plugin version |
Settings Structure
$fxm_settings = [
// General.
'default_restricted_message' => 'This content is for members only.',
'login_redirect_url' => '',
'after_login_redirect' => 'previous_page',
// Stripe.
'stripe_enabled' => false,
'stripe_mode' => 'test',
'stripe_test_secret_key' => '',
'stripe_live_secret_key' => '',
'stripe_webhook_secret' => '',
// PayPal.
'paypal_enabled' => false,
'paypal_mode' => 'sandbox',
'paypal_sandbox_client_id' => '',
'paypal_sandbox_secret' => '',
'paypal_live_client_id' => '',
'paypal_live_secret' => '',
'paypal_webhook_id' => '',
];
Membership Levels Structure
$membership_levels = [
'basic' => [
'name' => 'Basic',
'description' => 'Access to basic content',
'stripe_price_id' => 'price_xxxxx',
'paypal_plan_id' => 'P-xxxxx',
'capabilities' => [ 'basic_content' ],
'order' => 1,
],
'premium' => [
'name' => 'Premium',
'description' => 'Access to all content',
'stripe_price_id' => 'price_yyyyy',
'paypal_plan_id' => 'P-yyyyy',
'capabilities' => [ 'basic_content', 'premium_content', 'downloads' ],
'order' => 2,
],
];
Core Functions API
User Subscription Functions (includes/user-meta.php)
function fxm_user_has_active_subscription( $user_id = null );
function fxm_get_subscription_status( $user_id = null );
function fxm_get_user_membership_level( $user_id = null );
function fxm_user_has_capability( $capability, $user_id = null );
function fxm_update_user_subscription( $user_id, $data );
function fxm_get_user_by_subscription_id( $subscription_id );
function fxm_get_user_by_customer_id( $customer_id, $provider );
Content Restriction (includes/content-restriction.php)
function fxm_can_access_content( $required_level = null, $user_id = null );
function fxm_get_restricted_message( $level = null );
Stripe Functions (includes/stripe/)
function fxm_stripe_get_checkout_url( $price_id, $user_id = null, $args = [] );
function fxm_stripe_get_portal_url( $customer_id );
function fxm_stripe_verify_subscription( $subscription_id );
function fxm_stripe_handle_webhook();
function fxm_stripe_get_or_create_customer( $user_id );
PayPal Functions (includes/paypal/)
function fxm_paypal_get_subscribe_url( $plan_id, $user_id = null, $args = [] );
function fxm_paypal_get_portal_url( $subscription_id );
function fxm_paypal_verify_subscription( $subscription_id );
function fxm_paypal_handle_webhook();
function fxm_paypal_get_access_token();
Webhook Endpoints
URL Structure
/wp-json/fxm/v1/stripe/webhook
/wp-json/fxm/v1/paypal/webhook
Stripe Events to Handle
| Event | Action |
|---|---|
checkout.session.completed |
Create subscription, link to user |
customer.subscription.created |
Store subscription data |
customer.subscription.updated |
Update status, dates |
customer.subscription.deleted |
Mark as canceled |
invoice.payment_succeeded |
Update period end date |
invoice.payment_failed |
Update status to past_due |
PayPal Events to Handle
| Event | Action |
|---|---|
BILLING.SUBSCRIPTION.CREATED |
Store subscription data |
BILLING.SUBSCRIPTION.ACTIVATED |
Update status to active |
BILLING.SUBSCRIPTION.UPDATED |
Update subscription data |
BILLING.SUBSCRIPTION.SUSPENDED |
Update status to paused |
BILLING.SUBSCRIPTION.CANCELLED |
Mark as canceled |
PAYMENT.SALE.COMPLETED |
Update period end date |
Shortcodes
| Shortcode | Description | Attributes |
|---|---|---|
[fxm_restricted] |
Wrap restricted content | level, message |
[fxm_subscribe_button] |
Show subscribe buttons | level, provider, text |
[fxm_account] |
User account/subscription info | - |
[fxm_portal_link] |
Link to billing portal | text, provider |
[fxm_login_form] |
Login form for non-logged users | redirect |
[fxm_if_member] |
Conditional content for members | level |
[fxm_if_not_member] |
Conditional content for non-members | level |
Checkout Flow
Stripe Flow
- User clicks
[fxm_subscribe_button level="premium" provider="stripe"] - Generate Stripe Checkout URL with:
price_idfrom level configclient_reference_id= user_id (if logged in)success_url= site/membership-success/cancel_url= site/membership-cancel/customer_email= user email (if logged in)
- Redirect to Stripe Checkout
- User completes payment
- Stripe sends
checkout.session.completedwebhook - Plugin extracts
client_reference_id, matches/creates user - Stores subscription data in user meta
- User redirected to success_url with active subscription
PayPal Flow
- User clicks
[fxm_subscribe_button level="premium" provider="paypal"] - Generate PayPal subscription link with:
plan_idfrom level configcustom_id= user_id (if logged in)return_url= site/membership-success/cancel_url= site/membership-cancel/
- Redirect to PayPal
- User approves subscription
- PayPal sends
BILLING.SUBSCRIPTION.ACTIVATEDwebhook - Plugin extracts
custom_id, matches/creates user - Stores subscription data in user meta
- User redirected to return_url with active subscription
User Linking Strategy
For Logged-In Users
$checkout_args = [
'client_reference_id' => $user_id, // Stripe.
'custom_id' => $user_id, // PayPal.
'customer_email' => $user->user_email,
];
For Guest Checkout
$user = get_user_by( 'email', $customer_email );
if ( ! $user ) {
// Create new user from checkout data.
$user_id = fxm_create_user_from_checkout( $customer_data );
}
Hooks & Filters
Actions
do_action( 'fxm_subscription_created', $user_id, $subscription_data );
do_action( 'fxm_subscription_activated', $user_id, $subscription_data );
do_action( 'fxm_subscription_updated', $user_id, $subscription_data );
do_action( 'fxm_subscription_canceled', $user_id, $subscription_data );
do_action( 'fxm_subscription_expired', $user_id, $subscription_data );
do_action( 'fxm_before_checkout_redirect', $user_id, $level, $provider );
do_action( 'fxm_after_checkout_success', $user_id, $subscription_data );
Filters
apply_filters( 'fxm_restricted_message', $message, $level );
apply_filters( 'fxm_can_access_content', $can_access, $user_id, $level );
apply_filters( 'fxm_checkout_success_url', $url, $level );
apply_filters( 'fxm_checkout_cancel_url', $url, $level );
apply_filters( 'fxm_membership_levels', $levels );
apply_filters( 'fxm_level_capabilities', $capabilities, $level );
Security
- Webhook Verification - Verify signatures for both Stripe and PayPal
- Nonce Verification - All admin AJAX and forms use nonces
- Capability Checks - Admin pages require
manage_options - Input Sanitization - All inputs sanitized before storage
- Output Escaping - All outputs escaped before display
Implementation Phases
Phase 1: Core Foundation
- [x] Main plugin file with activation/deactivation hooks
- [x] Settings infrastructure and admin menu
- [x] User meta functions
- [x] Basic membership level storage
- [x] Helper functions
Phase 2: Stripe Integration
- [x] Stripe API wrapper functions
- [x] Checkout URL generation
- [x] Webhook endpoint and handler
- [x] Customer portal integration
- [x] Webhook signature verification
Phase 3: PayPal Integration
- [x] PayPal API authentication (OAuth)
- [x] Subscription link generation
- [x] Webhook endpoint and handler
- [x] Webhook signature verification
- [x] Billing portal redirect
Phase 4: Content Restriction
- [x] Shortcode registration
- [x] Content filtering
- [x] Level-based access checks
- [x] Restricted message display
Phase 5: Admin Interface
- [x] Settings page with tabs
- [x] Membership levels CRUD
- [x] Subscribers list with filters
- [x] Webhook status/logs
- [x] Export functionality
Phase 6: Frontend & Polish
- [x] Account shortcode/page
- [x] Subscribe button styling
- [x] Login/registration flow
- [ ] Email notifications (not yet implemented)
Setup Instructions
1. Stripe PHP Library
Download the Stripe PHP library and place it in vendor/stripe-php/:
# Download from https://github.com/stripe/stripe-php/releases
# Extract so that init.php is at: vendor/stripe-php/init.phpRead the full README on GitHub →
Releases
These releases are tags only. The author does not attach a packaged zip, so there are no download counts to report.