Skip to content

Conversation

@Salpertio
Copy link

📦 Consolidated Update: Subscriptions & Store Overhaul

Note: This PR replaces and merges all previously closed billing PRs into one streamlined update.

#460
#462

🚀 Overview

This PR implements PayPal Subscriptions, hardens our Stripe integration, and introduces a completely redesigned Storefront.


✨ Key Features

  • PayPal Integration: Added full support for monthly subscriptions + webhook handling for renewals.
  • Store Overhaul: Redesigned the UI with a modern, centered layout and improved mobile responsiveness.
  • Admin Settings: New Store tab to set price and new Settings to manage API keys for PP and Stripe

🛡️ Security & Hardening (Idempotency)

  • Double-Credit Protection: Added LastTransactionID tracking to prevent duplicate account credits from retried webhooks.
  • Smart Prevention: Users with an active subscription are now blocked from starting a new one to prevent accidental double-billing.
  • PayPal Auto-Cleanup: Automatically cancels a user's previous subscription if they accidentally create a new one.

⚡ Stripe Enhancements

  • Instant Re-Enable: Ported logic to ensure accounts are automatically unbanned/re-enabled immediately after a successful Stripe payment.
  • Billing Sync: Unified logic to focus strictly on Monthly billing cycles.

🛠 Technical Breakdown

View detailed changes
Area Change Description
Integrations PayPal REST SDK + Webhook listener (PAYMENT.SALE.COMPLETED)
Logic Ported "Instant Unban" logic to the Stripe provider service
Database Added LastTransactionID column to enforce idempotency
UI Updated Storefront components with modern CSS Flexbox layout

🧪 Testing Plan

  • PayPal: Create a test subscription and verify the user is credited.
  • Stripe: Pay with a disabled account and verify "Instant Unban" triggers.
  • Idempotency: Manually trigger the same webhook twice and ensure only 1 credit is applied.

*   **Stripe Integration:**
    *   Implemented full payment flow for paid invites using Stripe Checkout.
    *   Added Price and Currency fields to Invites in backend and frontend.
    *   Updated api-stripe.go to handle callbacks correctly.
    *   Added PaymentStatus verification in api-users.go.
*   **UI/UX Improvements:**
    *   Refactored Admin Create Invite form with aligned Price/Currency inputs.
    *   Added Payment Required modal to user registration flow.
    *   Improved Setup Wizard URL handling.
*   **Fixes:**
    *   Corrected price handling (dollars vs cents).
    *   Fixed setup wizard redirection bugs.
…json:"labels".

migrations.go: Updated the ombi.NotificationPref struct literals to use explicit keys (Agent:, UserID:, etc.).
jf_activity_test.go: Updated the mediabrowser.Time struct literal to use the explicit key Time:.
Dockerfile: Reverted to agnostic format.
- Implemented public store page (html/store.html) matched to system theme
- Added Stripe checkout integration for invite generation
- Updated webhook handler to process metadata (target_email, plan)
- Refactored Docker build process and added .dockerignore
- Fixed various struct literals and types for build stability
- Added 'Monthly Access' plan (.00/mo) to store page with shared email input
- Implemented 'invoice.payment_succeeded' webhook to extend user expiry on renewal
- Configured monthly invites to have strict 30-day expiry for auto-churn
- Refactored HandleWebhook to return raw events for flexibility
- Added cleanup for jfa-go binary in .gitignore
Adds an admin tab to set monthly price/currency and updates the store page UI to match. Removes the one-time 'Standard' plan entirely. Fixes a type assertion panic when saving config.
- Implemented full PayPal Monthly Subscription flow with webhooks.
- Implemented Stripe Monthly Subscription flow with webhooks.
- Added 'Store' tab to Admin UI for configuring both providers.
- Redesigned Store Page for better UI/UX.
- HARDENING: Added Idempotency checks (LastTransactionID) to prevent double-credit on retries.
- HARDENING: Added Auto-Cancel logic for PayPal to prevent double-billing.
- HARDENING: Added Smart Prevention to block active users from re-subscribing.
- HARDENING: Added Re-enable logic for both providers to instantly unban disabled users upon payment.
@netlify
Copy link

netlify bot commented Jan 3, 2026

👷 Deploy request for jfa-go pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit bb118b0

@Salpertio
Copy link
Author

🛒 Store Configuration & Setup Guide

This update introduces dynamic pricing and a complete UI overhaul (tacking shit onto what is already functional) for the "Pay-to-Generate" feature, focusing exclusively on Monthly Subscriptions.


🎨 Store Redesign Overview

  • New Layout: Modern, centered design with a streamlined email input field.
  • Conditional Logic: The store now intelligently toggles between Stripe and PayPal buttons based on your active configuration.
  • Cleanup: removed standard one time payment
  • fixed: race condition where pay to unlock would make an invite with more than 1 use stay unlocked and swapped to pay to generate instead

🅿️ PayPal Setup Guide

⚠️ Testing Note: PayPal Sandboxing is strict. You must use two separate sets of credentials:

  1. Business Account: Used to generate the API keys (The Seller).
  2. Personal Account: Used to "buy" the sub in the sandbox pop-up (The Buyer).
  3. its recommended you try the sandbox flow first to be sure it works.

1. Create your App

image

2. Create Product & Plan

You must create a specific billing plan for users to subscribe to:

to get there you will need to take note of as these contain the email and password creds to allow you to login
image

grab your business creds and head to https://sandbox.paypal.com
image

hit create
image

you will be prompted to start a new product
image

choose fixed note the next screen will ask you to name the plan (what the user sees on paypal)
image

since this isnt live you wont need to change anything besides add a number (dont go crazy with this the personal account has a limit)
image

after you set the payment continue to the next page skip the buttons page it drops you on
image

this will take you back to the subscription plans page. take note of the P-(ID) you'll need this
image

3. Configure jfa-go

collect your P-(ID), Client ID and Secret
Navigate to Settings > integrations > paypal and enter:

  • Enabled: True
  • Mode: Sandbox or Live
  • Client ID / Secret
  • Plan ID

💳 Stripe Setup Guide

1. Get API Keys

image image

after creation enter the sandbox and your api keys will be presented to you when you reach the sandbox page
copy the secret key as you will need this for later.

image

2. Configure Webhooks

Webhooks allow Stripe to tell your app when a payment succeeds or a sub is cancelled.

  1. Go to Webhooks and click Destination.
  2. Endpoint URL: https://your-domain.com/stripe/webhook
  3. Select Events: You must select these three:
    • checkout.session.completed
    • invoice.payment_succeeded
    • customer.subscription.deleted (Crucial for revoking access on cancel)

3. Finalize jfa-go

  1. Click Add Endpoint and reveal the Signing Secret (whsec_...).
  2. Go to Settings > integrations > Stripe in jfa-go.
  3. Enter your Secret Key and Webhook Secret.
  4. Save and Restart the service.

The Store page is now live at /store.

@Salpertio Salpertio marked this pull request as draft January 3, 2026 07:47
@Salpertio
Copy link
Author

Summary

We encountered some stability issues requiring a local rollback, but we have successfully re-implemented and hardened the integration.

Status Update

Stripe (🟢 Fully Functional):

  • 502 Errors Resolved: Implemented asynchronous email sending to prevent webhook timeouts.
  • Active Revocation: Verified. Manual cancellations immediately disable the user account.
  • Reliability: Metadata propagation fits ensuring new subscriptions carry the necessary data for automated management.
  • Cancel-at-Period-End: Logically covered by the same revocation handlers. (unsure about this need further testing)
    stripe has a time warp function but a bit complicated for today/potentially wont allow a sub created by the user through jfa to be sped up to initiate this

PayPal (🟡 Functional / Passive Mode):

  • Status: Payments work correctly.
  • Revocation: Webhooks for "Active Revocation" (disable user) dont seem to work right now (if at all).
  • Fail-Safe: We rely on the Passive Expiry Daemon as a fallback. When a user cancels, they stop paying, and JFA-Go automatically disables them once their paid time runs out. This ensures no free access is given, even without live webhooks.

i can see paypal being gutted entirely though. stripe works well

UI/UX Notes

  • The PayPal buttons function correctly but are currently a bit clunky compared to the stripe button styling. We can refine the CSS in a future PR.

Ready for review.

@hrfee
Copy link
Owner

hrfee commented Jan 5, 2026

This is very big and I want to rework it to fit the style of the rest of the code so it'll take a long time.

@hrfee
Copy link
Owner

hrfee commented Jan 5, 2026

Also, don't worry about the review notes, those are for me.

@Salpertio
Copy link
Author

Also, don't worry about the review notes, those are for me.

noted, congrats, new grad.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants