Sure! I actually checked back and confirmed I used Claude. You may want to go with the $20/mo subscription to get this done. I'd just paste below into Claude and have it get started for you. It can browse the sites for you, extract everything it needs, and connect to your cpanel instance and whatever else it may need. Pretty impressive, and after about an hour of convo, it got it done for me!
Migrating aMember subscriptions to XenForo user upgrades
Oct 13, 2026 ·
@Matt Morton
Overview
This process moves every active aMember subscription onto XenForo 2.3 native user upgrades without any member re-subscribing, re-entering a card or being charged early. Existing Stripe and PayPal billing continues at the same price and on the same renewal date; only the system that tracks it changes. Afterwards aMember is retired completely: billing, login and its folder.
It fits a XenForo forum where aMember sells a membership (a paid user group) through Stripe and PayPal recurring subscriptions, and where aMember and XenForo share one database. The work is done with a small custom XenForo add-on (call it the bridge add-on) that adopts each member and keeps legacy PayPal renewals working, plus configuration in Stripe, XenForo and the web server.
| Who | What happens to them |
|---|
| Stripe subscribers | Get a real Stripe subscription on the card already saved; first charge lands on their existing renewal date |
| PayPal subscribers | Keep billing in PayPal exactly as before; PayPal's payment notifications now extend their XenForo upgrade |
| Comped / prepaid members | Get a non-billing XenForo upgrade ending on the same date (or permanent) |
| Members paying twice | Held back for a manual decision, then one invoice is adopted |
How it works
The core idea:
leave every payment method where it is and move only the bookkeeping. Members keep their saved card or PayPal agreement, their price and their renewal date. XenForo becomes the system that grants and removes access.
- Stripe. aMember never created Stripe Subscriptions; it charged saved cards from its own cron. The migration creates a real Stripe Subscription for each member on their existing customer and payment method, with a free trial that ends on their old renewal date, so the first charge lands exactly when aMember would have charged. The subscription carries a XenForo purchase request key in its metadata, and the member gets an active XenForo upgrade running to that date plus a few days of grace. From then on, Stripe bills and XenForo's own webhook handler extends the upgrade.
- PayPal. PayPal Standard subscriptions can't be moved or re-pointed. They keep billing and keep posting IPNs to the old aMember URL. An .htaccess rewrite delivers those to XenForo's PayPal callback, and a small extension looks up the XenForo purchase request by the PayPal subscription ID, because the IPN's custom field still holds aMember's value.
- Comped members get a XenForo upgrade with a fixed or permanent end date and nothing at the gateway.
- aMember is told the adopted invoices are cancelled and its cron is shut off, so it can never charge anyone again.
[embed: node/91f2958a-8fe9]
Renewals from both gateways end in the same place: XenForo extends the member's upgrade and the paid group stays on. Nothing about this asks the member to do anything.
Phase 1: Discovery
Before writing any migration code, get a read-only picture of what aMember, Stripe, PayPal and XenForo each think is true. Every decision later in the process comes from this.
Export (read-only). Dump the aMember tables (am_user, am_invoice, am_invoice_payment, am_access, am_product, am_billing_plan and the Stripe/PayPal plugin data) and the XenForo tables involved (xf_user, xf_user_upgrade, xf_user_upgrade_active, xf_user_upgrade_expired, xf_purchase_request, xf_payment_profile, xf_payment_provider_log, xf_user_group_change). Exclude password hashes and any gateway API keys or secrets from the export; you do not need them.
Questions to answer:
- Does aMember actually use Stripe Subscriptions? Usually it does not. aMember's Stripe plugin stores a Stripe customer and a saved payment method, then charges the card itself from its own cron on each rebill date. If Stripe shows customers and payments but no Subscriptions, that is what is happening, and it means turning aMember off stops all Stripe renewals.
- What do the PayPal subscriptions look like? Older installs use PayPal Standard recurring payments. Note each member's PayPal subscription ID (I-… or S-…) and the IPN URL PayPal is calling. Check for legacy IPN listener URLs from an earlier platform (for example an old WordPress paywall plugin) that PayPal may still be posting to.
- Who is paying twice? Look for members with more than one active recurring invoice, or with both an aMember subscription and a XenForo purchase. Decide which one to keep before cutover; the other must be cancelled at the gateway.
- Who has access without paying? Comped or lifetime members (often a far-future expiry date) need a plan of their own: a permanent upgrade, a long fixed end date, or a plain user group.
- How is the paid group assigned? If aMember sets the paid group as members' primary group, XenForo upgrades cannot remove it later. See Gotchas.
- Is XenForo's own Stripe webhook working? Check the payment provider log for failed or unmatched callbacks. A broken webhook means XenForo checkouts may have charged people without granting the upgrade.
- Did anyone already buy through XenForo? Those members need to be excluded from adoption, or reconciled by hand.
Turn the answers into a per-member plan: adopt via Stripe, adopt via PayPal, comp, skip, or handle manually. The migration tooling should print this plan in a dry run before changing anything.
Phase 2: Prerequisites
Set these up before touching any member. None of them affects existing billing on its own.
XenForo user upgrades. Create one recurring upgrade per aMember plan (for example monthly and yearly), with the same price, currency and length the members currently pay. The upgrade should add the paid user group. If the price or length differs from what Stripe or PayPal will charge, XenForo's cost check rejects the renewal.
Stripe webhook for XenForo. In the Stripe Dashboard, add an endpoint pointing at XenForo's callback (https://<your-site>/payment_callback.php?_xfProvider=stripe) and subscribe it to these events:
- checkout.session.completed
- charge.succeeded
- invoice.payment_succeeded
- charge.refunded
- charge.dispute.funds_withdrawn
- charge.dispute.funds_reinstated
- review.opened
- review.closed
The Dashboard only lets you choose the account's default API version or the latest one. On recent versions, Stripe's first $0 trial invoice fails XenForo's amount check (see Gotchas); the bridge add-on handles that. Copy the endpoint's signing secret into XenForo's Stripe payment profile yourself; never paste it into a chat, ticket or shared doc.
Stripe customer emails. Decide whether Stripe should email receipts, upcoming-renewal or trial-ending notices. Adopted subscriptions start in a trial that ends on the member's renewal date, so a "your trial is ending" email would confuse members. Turning Stripe's billing emails off also turns off failed-payment emails, so plan another way to tell members their card failed.
PayPal profile. XenForo needs a PayPal payment profile using the legacy PayPal (Standard) provider, with the same receiving email the PayPal subscriptions pay into. It can be inactive for new purchases; it only needs to exist so renewals have somewhere to land.
Bridge add-on. Install a small custom add-on that:
- extends XF\Payment\PayPal so an IPN carrying an aMember custom value is matched to a XenForo purchase request by its PayPal subscription ID;
- extends XF\Payment\Stripe to acknowledge $0 trial invoices instead of logging them as errors;
- adds CLI commands: preflight (sanity checks), adopt (the migration, dry run by default) and groups (fixes primary groups).
Backups. Take a full database backup and a copy of the aMember folder before Phase 3.
Phase 3: Pilot and dry run
Commands below use a generic bridge: prefix; use whatever your add-on registers. All of them run from the XenForo root with php cmd.php.
1. Preflight. php cmd.php bridge

reflight is read-only. It should confirm the payment profiles exist and are configured, the upgrades match aMember's prices and lengths, the Stripe webhook secret is set, and list anything that will block adoption: duplicate subscriptions, members already holding a XenForo upgrade, aMember accounts with no matching XenForo user, and users whose primary group is the paid group.
2. Dry run. php cmd.php bridge:adopt with no --live flag writes nothing. Read the whole plan. Every active aMember member should land in exactly one bucket (Stripe, PayPal, comp, skip or blocked), with the next charge date and amount shown for the recurring ones. Fix the data or the tool until the plan is right.
Useful options for the adopt command:
| Option | What it does |
|---|
| --live | Actually write to Stripe and XenForo. Without it, nothing changes. |
| --invoice=<id> | Only this aMember invoice (repeatable). Also the way to adopt a member flagged as a duplicate, once you've picked which subscription to keep. |
| --only=stripe,paypal,comp | Limit to certain kinds. |
| --limit=<n> | Stop after n adoptions. |
| --comp-permanent | Turn far-future comps into permanent upgrades. |
| upgrade and profile ids | Which XenForo upgrade and payment profile each kind maps to. |
3. Pilot one member. Pick a Stripe member whose next charge is weeks away (a yearly member is ideal) and run php cmd.php bridge:adopt --invoice=<id> --live. Then check by hand:
- In Stripe: the customer now has one Subscription, status trialing, trial ending on the old renewal date, the right price, the existing default payment method, and the XenForo request key in its metadata. No charge was made.
- In XenForo: the user has an active upgrade under "Purchased upgrades" with the right end date, is in the paid group, and the purchase request shows the Stripe subscription ID.
- In aMember: the old invoice is cancelled, so aMember's cron will never charge it again.
If you also adopt PayPal members, pilot one PayPal member too and confirm the purchase request stores the PayPal subscription ID. PayPal renewals can only be verified when the next IPN arrives, so start with someone renewing soon and watch the payment provider log.
Phase 4: Cutover
Do these in order, in one sitting. The order matters: aMember must stop charging before Stripe subscriptions exist, and PayPal IPNs must reach XenForo before any renewal arrives.
1. Stop aMember's cron. Turn off aMember's cron in its settings and remove any cron job that calls it. Then check whether something external still calls the cron URL ("Use external cron" can be enabled even with no cron job you can see). Block it in amember/.htaccess to be sure:
RewriteEngine On
RewriteRule ^cron(\.php)?$ - [F,L]
2. Route PayPal IPNs to XenForo. PayPal keeps posting renewals to the URL saved on each subscription, which you cannot change. Rewrite it internally (no redirect, since PayPal does not follow redirects for IPN) in amember/.htaccess:
RewriteRule ^payment/paypal/ipn$ /payment_callback.php?_xfProvider=paypal [L,QSA]
If discovery found older listener URLs (for example a WordPress paywall plugin's ?listener=IPN style query string on the site root), add matching rules in the root .htaccess that rewrite them to the same target. Test each with curl -X POST against the old URL: a XenForo response such as "Request not validated" means the request reached XenForo; an aMember page or a 404 means the rule is wrong.
3. Adopt everyone. php cmd.php bridge:adopt --live (add --comp-permanent if you chose that). The tool cancels each adopted aMember invoice as it goes. Read the summary: every planned member should show as adopted, and any failures should be retried with --invoice=<id> after fixing the cause. The command is safe to re-run; already-adopted members are skipped.
4. Handle duplicates. For each member paying twice, adopt the subscription you are keeping with --invoice=<id> --live, then cancel the other one in Stripe or PayPal and refund if needed.
5. Fix primary groups. php cmd.php bridge:groups (dry run) lists users whose primary group is the paid group, and which of them would lose access because they have no active upgrade. Review that list, then run it with --live. It moves them to the Registered primary group and keeps the paid group as a secondary group for anyone with an active upgrade.
6. Verify. Run preflight again; it should come back clean. The number of users in the paid group should match the number of active aMember members you started with (plus or minus the ones you deliberately skipped).
Phase 5: Retire aMember
Once cutover is verified, take aMember out of the picture in stages, keeping a way back for 30 days.
Right away:
- Back up. Dump the database and archive the aMember folder (tar czf ~/amember-files-<date>.tgz amember, run from the folder that contains the aMember folder, usually the web root). Run each backup command once; re-running can overwrite a good archive.
- Hand login and registration back to XenForo. Enable XenForo registration, disable any add-on or setting that sent login, signup or lost-password to aMember, and repoint navigation links and the "no permission" phrase that pointed members at aMember's signup page to XenForo's account upgrades page. Test login, registration, lost password and outgoing email as a logged-out user.
- Redirect the old URLs. In the root .htaccess, keep the PayPal IPN rewrite working without the folder, then send everything else under the old path to the upgrades page:
RewriteRule ^amember/payment/paypal/ipn$ /payment_callback.php?_xfProvider=paypal [L]
RewriteRule ^amember(/.*)?$ /account/upgrades [R=301,L]
- Move the aMember folder out of the web root rather than deleting it.
- Disable aMember's Stripe webhook endpoint in the Stripe Dashboard (disable, don't delete yet).
- Optionally, add a short note to the upgrades page for existing members: their membership carried over and nothing is needed from them.
After 30 days, once renewals have come through cleanly:
- Delete the moved aMember folder and the backups you no longer need.
- Uninstall the login-redirect add-on.
- Export the am_ tables to an archive, then drop them. Do this after any refunds for double-billed members are settled, since those tables are your payment history.
- Delete the disabled Stripe endpoints.
Keep the bridge add-on installed for as long as any adopted PayPal subscription is still active. Its PayPal extension is what matches those renewals to members. The Stripe side doesn't depend on it except for quieting the $0-invoice errors.
Gotchas
Things that cost time or nearly caused a problem.
- The primary-group trap. XenForo upgrades add and remove the paid group as a secondary group. If aMember made it the primary group, nobody would ever lose access when an upgrade expires. Worse, XenForo leaves a group out of the secondary list while it is the primary group, so simply changing the primary group can strip access from paying members. The groups fix has to re-add every group the user's active upgrades grant (from xf_user_group_change) when it moves the primary group.
- A hidden external cron. aMember's "use external cron" setting can be on with no cron job visible in the hosting panel, and something outside can still be calling the cron URL. Block it in .htaccess; don't rely on the setting.
- Stripe API version and $0 invoices. The Dashboard won't let you pick an older API version for a new endpoint. On recent versions, the first invoice of a trialing subscription is $0 and XenForo logs it as a cost mismatch. Harmless, but it fills the log with errors; the bridge extension skips invoice.payment_succeeded when the amount paid is 0. Real renewals still go through the normal check.
- Refunds on recent API versions. A refund issued from Stripe may not automatically downgrade the member. Check after any refund and remove the upgrade by hand if needed.
- Stripe billing emails off means no failed-payment emails. If you turn them off to avoid "trial ending" confusion, members whose card fails won't hear about it from Stripe. Watch failed invoices for the first cycle and contact those members yourself.
- Cards that look expired. Some saved cards show old expiry dates but still charge successfully because the card network updated them. Don't skip a member only because the card looks expired; let the renewal tell you.
- Template modifications need development mode. XenForo only lets you add template modifications in dev mode. On a live site, a direct template edit (noted somewhere so it survives upgrades) or a Page node is simpler.
- Run destructive commands once. Re-running a backup that writes to the same filename, or an adopt without checking the previous run's output, is how good state gets overwritten.
- The PayPal legacy provider is deprecated. XenForo still ships the PayPal Standard provider, but a future release could drop it. If that happens, the remaining PayPal subscribers will need to re-subscribe through a supported gateway. Track how many are left.
- Members with no matching XenForo account. Match by email first, and treat a username-only match as a blocker for manual review, not an automatic adoption.