WordPress / wordcamp.org

WordCamps are casual, locally-organized conferences covering everything related to WordPress.
https://wordcamp.org
125 stars 69 forks source link

Stripe checkout sessions may timeout, with valid transaction #1255

Open dd32 opened 6 months ago

dd32 commented 6 months ago

Describe the bug

As a follow up to #1227, it's possible for a ticket purchaser to purchase a ticket on the stripe side, but not have a ticket assigned to them on WordCamp.org.

Why does this happen? The stripe flow is as such:

  1. Click Purchase
  2. Setup attendee, set status as not-paid/draft
  3. Redirect to Stripe
  4. Pay on Stripe
  5. Redirect back to WordCamp
  6. Set attendee status
  7. Everyone is happy.

If step 5/6 doesn't happen, 7 doesn't happen.

The previous Stripe flow was something like this:

  1. Capture authorization
  2. Submit form
  3. setup attendee
  4. Capture payment & set status
  5. Everyone is happy

Steps 2~4 would happen in the same HTTP request, so there was no potential for a transaction being missed.

Potential fixes In order to combat this, we could do one of...

Using Stripe Webhooks is the obvious choice. The paypal integration uses something similar.

To reproduce

Steps to reproduce the behavior:

  1. Attempt to purchase a ticket
  2. Click on Purchase; redirect to Stripe
  3. prevent requests to $city.WordCamp.org from your browser
  4. Complete the transaction on Stripe, watch the failed network requests after redirect back to us.
  5. See ticket still in draft

This is an example of such a ticket: https://vienna.wordcamp.org/2024/wp-admin/post.php?post=3074&action=edit

dd32 commented 6 months ago

Implement the Stripe Webhook system, so that we get a webhook request when the ticket purchase completes

The way the Paypal code works, is that the notify_url is set, which pings the individual WordCamp site with the webhook/IPN.

Stripe seems to only have global webhooks, which means that the webhook ping wouldn't be site-specific.

That means we'd have to have a generic webhook endpoint that then switched to the appropriate site before processing it. (edit: Probably can't switch, so it'd be a self-call like Stripe -> Generic WordCamp webhook -> self-request to WordCamp-specific webhook API.)

When the ticket is to go to timeout, ensure that there exists no stripe transaction for the checkout.

1256 is a PR that implements this option; which might be good enough, depending on how often these missed transactions occurs.

dd32 commented 6 months ago

I've done a sanity check on existing transactions, and I'm not seeing any Stripe checkout sessions that were paid and that didn't have a follow up publish attendee action.

(Edit: For completeness sake; There is a single expected entry, which is what caused this ticket to be created.)

dd32 commented 3 months ago

Ran into this again today; where a user never came back to the WordCamp site after Stripe checkout. The user somehow ended up refreshing the stripe checkout page a few times until stripe kicked them out with a "Link has expired", never proceeding back to the return_url to WordCamp to finalise the transaction.

1256 would've caught this, except that we weren't storing the metadata correctly. That's fixed via 2949cdae82989ced985bb442a32496beb3b5c672

(Sanity check run; still no other transactions found that are affected)

The downside to that is that we're only checking ~24hrs after checkout, where as a customer wouldn't wait that long before complaining (Such as in this case, the ticket fell in my lap less than 12hours later)

That means that out of the 3 suggested solutions above, we should probably still either have the timeout job run more often (but still only transition >24hrs) or implement webhooks, such that we know about the status within the minute, rather than 24hrs later with the current timeout system.