Some time earlier this year, my membership payments and renewals stopped working.
Renewal payments made via Stripe are not updating the EOT date for past members and as a result their membership status is being cancelled. This happens both for people renewing manually and for subscriptions that trigger a Stripe payment automatically.
New member payments are not changing the membership status, nor completing any of the payment details, including setting the EOT date.
I have turned debugging on, so that I can see that the Stripe API interface is functioning - or at least that information is coming from Stripe.
Since I turned logging on, there has been one payment of each type and there are numbers of log messages on each of the days on which payment was made (10 April, 23 April and 26 April). There are also log messages for intervening days, presumably triggered by CRON-jobs? The log messages look as though things are working - the names of the people who made the payments appear in the messages, and I can find comforting bits of text that say things like “customer.updated”. But the s2Member records are NOT being updated. As a result, a couple of people have paid twice, which is a nightmare. I am solving the problem temporarily by logging into Stripe, checking for payments, and when I find them, manually updating the appropriate s2Member record. This is not a viable solution - if no one can help me on this I’m going to have to change to another membership system.
I can send the log file to anyone who wants to help.
Stripe payments are no longer updating s2Member records
You could try the new Stripe option of not checking ipn vars under Stripe options in s2. Maybe you have a problem there and yes the result the is that new payments are correctly registered but renewals and eot due to failed payments on subscription not.
Thanks for the advice. Do you mean the new option "Enable Fallback for Missing User “IPN Signup Vars”? (beta). And if so, are you recommending setting it to Yes instead of the default which is No?
I think that’s what you’re recommending, just wanted to be sure before I change anything.
Yes
I have a new clue as to why the renewals aren’t working. I checked the gateway-core-ipn.log file (haven’t looked at it before) and found the following.
When renewals and payments stopped working some time between 21 October and 17 Nov 2024, this message appears in the log file for each payment:
[s2member_log] => Array
(
[0] => IPN received on: Sun Nov 17, 2024 7:54:53 am UTC
[1] => s2Member POST vars verified with a Proxy Key
[2] => s2Member originating domain (`$_SERVER["HTTP_HOST"]`) validated.
[3] => Not processing. Duplicate IPN.
[4] => s2Member `txn_type` identified as a type of EOT.
[5] => Duplicate IPN. Already processed. This IPN will be ignored.
)
Up to and including 21 Oct 2024, renewals and payments worked. In the log I can see this:
[s2member_log] => Array
(
[0] => IPN received on: Mon Oct 21, 2024 5:07:32 am UTC
[1] => s2Member POST vars verified with a Proxy Key
[2] => s2Member originating domain ($_SERVER["HTTP_HOST"]
) validated.
[3] => s2Member txn_type
identified as ( web_accept|subscr_signup
).
[4] => s2Member txn_type
identified as ( web_accept|subscr_signup
) w/ update vars.
[5] => Automatic EOT (End Of Term) Time set to: Tue Nov 4, 2025 12:19:42 am UTC.
[6] => s2Member Level/Capabilities updated w/ advanced update routines.
[7] => Modification Confirmation Email sent to: “Rosemary Houseman” rosemaryhouseman@icloud.com; admin@continuo.org.au.
[8] => Modification Notification URLs have been processed.
[9] => Subscr. Return ( modification=1
), a Proxy Return URL is ready.
[10] => User exists. Handling payment
for Subscription via ( web_accept
).
[11] => Storing IPN signup vars now. These are associated with a User’s account record; for future reference.
)
I’m going to take the advice of Felix Hartmann above, but I’m not hopeful that it’s going to fix the problem as it seems to relate to missing IPN vars, not duplicate IPNs.
One more piece of information:
I manage two sites which use s2Member and Stripe. On the one that is working, there is NO Stripe webhook.
Hi Helen.
Please help me understand the problem better. Is the problem that the new signup payment doesn’t give the user the s2 access level? Or is the problem when the subscription is cancelled/ended is an EOT time not set? Some other scenario?
Is it happening with new signups (paying and creating a new account on your site), or with existing users paying to upgrade?
“customer.updated”. But the s2Member records are NOT being updated.
Recurring payments of an active subscription are handled by Stripe and s2 doesn’t act on those, there’s nothing to update on the user’s profile, he keeps his access and no EOT is set. https://s2member.com/kb-article/when-is-an-eot-time-set-for-each-user/
s2Member only acts on a few webhooks from Stripe, not all of them:
invoice.payment_succeeded
invoice.payment_failed
customer.deleted
customer.subscription.deleted
charge.refunded
charge.dispute.created
When it’s some other one, s2 just doesn’t do anything with it.
a couple of people have paid twice
How do those happen? Does the user try again, or is the site charging twice from a single form submission? What would I need to do to reproduce that?
I’d like to see your log files, if that’s okay. You can email me the zip to support @wpsharks.com. Please add a link to this thread in your email.
I kinda guess it’s user database related. Maybe something similar to me back then entering the domain into the wrong field.
Hi Christian
Both renewals and new membership payments were working OK until about October 2024.
Since then:
Members who don’t have a recurring subscription membership receive a warning a week before their membership is due to expire. When they pay as a result of this or later renewal prompts, their Stripe payment doesn’t update s2 and as a result they are demoted back to free subscribers when the EOT is reached. They then receive further messages which has resulted in some people paying twice.
People can’t join as members unless they are already free subscribers. When a free subscriber pays their membership, they are not promoted to member level, nor are any of the details of their payment recorded, nor is an EOT time set.
I am currently solving the problem by watching the Stripe account for new transactions, then manually making the changes that s2 is not doing.
Hope this makes the issues clear.
I’ll zip and send the log files as requested. Sorry for not replying sooner, but I’m not getting notifications when someone adds to the thread, even though it shows as “Watching”.
Helen
Hope this makes the issues clear.
Thanks for the additional details. So user gets reminder about payment, he pays, but s2Member still doesn’t update the EOT time…
Members who don’t have a recurring subscription membership receive a warning a week before their membership is due to expire.
Okay, so they’re not subscriptions in Stripe, they paid only once (buy-now). That means that the EOT time got set immediately in their profile when they paid.
When they pay as a result of this or later renewal prompts, their Stripe payment doesn’t update s2 and as a result they are demoted back to free subscribers when the EOT is reached
How are they paying? Is it with the s2Member pro-form again? Is the EOT behavior set to extend the user’s term? WP Admin > s2Member Pro > Stripe Options > Automatic EOT Behavior > Fixed-Term Extensions (Auto-Extend)
They then receive further messages which has resulted in some people paying twice.
Again: are they paying through the s2Member pro-form? s2 won’t recognize payments by other means than s2’s, so wouldn’t update the user’s account.
Also, is the user logged into his account when he makes the payment? s2 won’t credit the existing account if he’s logged out, and instead the payment would go towards a new account. From what you said, I’m guessing that the payment pro-form isn’t shown to guests (i.e. logged out), but thought I’d ask.
When a free subscriber pays their membership, they are not promoted to member level, nor are any of the details of their payment recorded, nor is an EOT time set.
That’s odd. Are you using the s2 pro-form for Stripe? What do the logs say?
I’ll zip and send the log files as requested.
Thanks.
Sorry for not replying sooner, but I’m not getting notifications when someone adds to the thread, even though it shows as “Watching”.
I’ll check if the forum’s emails are going out correctly. Did you check in your spam folder if they’re ending up there?
Answers to the questions -
yes all the payments (first payment, subscription and renewal) come through the s2Member pro-form and yes the EOT behavior is set to extend
yes - you can only pay if you’re logged in.
The weird thing about this is that it worked as expected from 2021 when I built the website until October 2024. Then it stopped and I’m not aware of having changed anything relating to s2Member at that time. Since then I’ve tried reconnecting Stripe and removed all the webhooks except those that s2Member acts on, but no change.
Also, I’ve checked spam files both in my mail client and on my mail server, no notifications.
Thanks, Helen.
Just saw your email with the logs, thanks! Okay…
Okay, I see that you sell subscription (recurring) and buy-now (single payment) annual access.
I see that the gateway-core-ipn.log entries from late last year to now are all skipped with “Not processing. Duplicate IPN.” Every one, which is obviously wrong. I see it done for both: buy-nows and recurring payments.
Looking at the code, the only thing I see that could cause this message for all of them, is if there were a problem with this condition:
if(!get_transient($transient_ipn = 's2m_ipn_'.md5('s2member_transient_'.$_paypal_s)) && set_transient($transient_ipn, time(), 31556926 * 10))
If that fails, it ends up in the part that has the message you keep getting for everything. Seems like the transient is being found on every IPN, so the system thinks it’s a duplicate and ignores it.
I wonder if something changed around that time you mention, that affected transients… Maybe some plugin you installed, or some configuration in your setup? Would be interesting to do some tests about it…
First suspect that comes to mind would be Object Caching… It could be that it’s always returning the same cached transient? Object Caching has always given trouble, and I’d recommend that you verify it’s off and disabled.
Maybe you can add this to your /wp-content/mu-plugins/ dir, create a php file, e.g. s2-transient-troubleshooting.php
<?php
add_filter('c_ws_plugin__s2member_paypal_notify_in_subscr_or_wa_w_level', function($paypal, $defined_vars) {
if (empty($paypal) {
return $paypal;
}
global $wpdb;
// Use existing defined vars
if (isset($defined_vars['_paypal_s']) && isset($defined_vars['transient_ipn'])) {
$_paypal_s = $defined_vars['_paypal_s'];
$transient_ipn = $defined_vars['transient_ipn'];
} else {
$paypal['s2member_log'][] = "DIAGNOSTIC ERROR: _paypal_s or transient_ipn not found in defined vars.";
$paypal['s2member_log'][] = "DIAGNOSTIC: Available defined_vars: " . print_r($defined_vars, true);
return $paypal;
}
// Transient checks
$wp_transient_exists = get_transient($transient_ipn) !== false;
$db_key = '_transient_' . $transient_ipn;
$db_value = $wpdb->get_var($wpdb->prepare(
"SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
$db_key
));
$db_transient_exists = $db_value !== null;
$cache_value = wp_cache_get($transient_ipn, 'transient');
$cache_transient_exists = $cache_value !== false;
// Build clear log message
$log_msg = "DIAGNOSTIC: PayPal txn_id=" . ($paypal['txn_id'] ?? 'N/A') .
" | Transient Key: $transient_ipn" .
" | Serialized Data Sample: " . substr($_paypal_s, 0, 200) . "..." .
" | Transient Exists (WP get_transient): " . ($wp_transient_exists ? 'YES' : 'NO') .
" | Transient Exists (DB Query): " . ($db_transient_exists ? 'YES' : 'NO') .
" | Transient Exists (Object Cache): " . ($cache_transient_exists ? 'YES' : 'NO') .
" | Subscription ID: " . ($paypal['subscr_id'] ?? 'N/A');
$paypal['s2member_log'][] = $log_msg;
return $paypal;
}, 10, 2);
Make sure the file starts with the <?php
with no space or line before the opening <
Then we should get some extra details in the next IPN log entries…
Thanks for all the detective work. I do have object caching on - have just disabled it, but not yet tested to see if it solves the problem. I’ll let you know as soon as I do.
I use w3 Total Cache - it’s possible that around the time that things stopped working there was a new version with new default settings (as I don’t remember making a conscious decision to use object cache - I usually opt for minimal caching because it can create problems) .
Helen
Thanks! Well, that may just be the culprit… Okay, let’s wait for a new one to come in, or just do a test payment yourself (50 cents is the min for live ones with Stripe).
HALLELUJAH! And other words of praise. Thanks so much Cristián!
I was going away for a few days and didn’t have time to organise a test before I went, but someone renewed their membership while I was away and it all worked as it should. Just waiting now for a new membership, but I’m sure you’ve solved it for me. And it explains how it went from OK to not OK without any apparent change to the website.
So relieved not to have to switch from s2Member.
Helen
That’s great news! Well done. I’m so glad you found the source of the problem. Object Caching can be so sneaky and troublesome…
I now updated the Troubleshooting Tips article to include Object Caching. https://s2member.com/kb-article/common-troubleshooting-tips/
If you feel like it and is not too much to ask, would you leave me a rating over at wordpress? It’d be very appreciated. https://wordpress.org/support/plugin/s2member/reviews/
The only object caching that works really well is via APCU, you will need a very recent nginx/PHP version for it to work and there is 10-20min lag and sadly no easy way to flush. But it’s really efficient and doesn’t mess up like any other object caching. Any of those typical object caching plugins either mess up or don’t actually reduce load. Object caching has to be done at the actual backend of your server not wordpress level .
Actually it’s so efficient you could ditch anything else except page caching for not logged in users as usual which is always the fastest.
Just need to disable it when updating your layout or debugging Plugins, adding new content or similar. As it’s actually backend it will affect you as admin just like any other user.
It reduces php calls for me by around 95%.
Thanks for sharing that, Felix!