Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling charge expiration webhooks #3651

Merged
merged 8 commits into from
Dec 12, 2024

Conversation

wjrosa
Copy link
Contributor

@wjrosa wjrosa commented Dec 10, 2024

Fixes #1378

Changes proposed in this Pull Request:

When setting up a store to authorize and capture later, the payment for an order may never be captured. When this happens, Stripe expires the transaction after ~7 days.

Currently, we don't have any handlers for this event. Expired orders stay unchanged on the store side.

This PR adds a handler for them, updating the status to failed and including a note with the corresponding information.

Testing instructions

  • Checkout and build this branch on your test environment (add/handling-charge-expiration-webhooks)
  • Connect your Stripe account
  • Enable manual capture in wp-admin/admin.php?page=wc-settings&tab=checkout&section=stripe&panel=settings ("Issue an authorization on checkout, and capture later")
  • As a shopper, add a product to your cart
  • Complete the checkout
  • As a merchant, confirm the the order was set to on-hold
  • Trigger the charge expiration event:
    -- Option 1: wait the payment to expire. 7 days for card and 60 minutes for Affirm. This requires the store to be publicly accessible
    -- Option 2: copy the following event body sample:
Sample event
{
  "object": {
    "id": "ch_3QRf2fGwEEw4tjx01Ufql8i8",
    "object": "charge",
    "amount": 5500,
    "amount_captured": 0,
    "amount_refunded": 5500,
    "application": "ca_BC3QunyBFJIQULdxege9Gyg8WjKI1vXD",
    "application_fee": null,
    "application_fee_amount": null,
    "balance_transaction": null,
    "billing_details": {
      "address": {
        "city": "Random City",
        "country": "NZ",
        "line1": "1600 Random Address",
        "line2": null,
        "postal_code": "3015",
        "state": "WGN"
      },
      "email": "[email protected]",
      "name": "Card Holder Name",
      "phone": "+1 650-555-5555"
    },
    "calculated_statement_descriptor": "USELESS-EATER.JURASSIC",
    "captured": false,
    "created": 1733166741,
    "currency": "usd",
    "customer": "cus_RFPozHGCKF7lO9",
    "description": "WooCommerce Stripe Dev - Order 147",
    "destination": null,
    "dispute": null,
    "disputed": false,
    "failure_balance_transaction": null,
    "failure_code": null,
    "failure_message": null,
    "fraud_details": {
    },
    "invoice": null,
    "livemode": false,
    "metadata": {
      "customer_email": "[email protected]",
      "customer_name": "Card Holder Name",
      "order_id": "147",
      "order_key": "wc_order_shjWSILrZUUAh",
      "payment_type": "single",
      "signature": "147:dc9a096f7ef2036df0946723c4eb81c1",
      "site_url": "http://localhost:8082"
    },
    "on_behalf_of": null,
    "order": null,
    "outcome": {
      "network_advice_code": null,
      "network_decline_code": null,
      "network_status": "approved_by_network",
      "reason": null,
      "risk_level": "normal",
      "risk_score": 32,
      "seller_message": "Payment complete.",
      "type": "authorized"
    },
    "paid": true,
    "payment_intent": "pi_3QRf2fGwEEw4tjx01A0gYgI8",
    "payment_method": "pm_1QMv10GwEEw4tjx06McQy2nY",
    "payment_method_details": {
      "card": {
        "amount_authorized": 5500,
        "authorization_code": null,
        "brand": "visa",
        "capture_before": 1733771541,
        "checks": {
          "address_line1_check": "pass",
          "address_postal_code_check": "pass",
          "cvc_check": null
        },
        "country": "US",
        "exp_month": 12,
        "exp_year": 2024,
        "extended_authorization": {
          "status": "disabled"
        },
        "fingerprint": "NGxtOcphGPgIzOko",
        "funding": "credit",
        "incremental_authorization": {
          "status": "unavailable"
        },
        "installments": null,
        "last4": "4242",
        "mandate": null,
        "multicapture": {
          "status": "unavailable"
        },
        "network": "visa",
        "network_token": {
          "used": false
        },
        "overcapture": {
          "maximum_amount_capturable": 5500,
          "status": "unavailable"
        },
        "three_d_secure": null,
        "wallet": null
      },
      "type": "card"
    },
    "radar_options": {
    },
    "receipt_email": null,
    "receipt_number": null,
    "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xT1V3YjZHd0VFdzR0angwKJaC3boGMgYaoRVYO8o6LBafjNK0v2jtmwIcozIk41LVm33M2SidTTuH1MrL_YjIHv6g6Bgk35hC-XY5",
    "refunded": true,
    "review": null,
    "shipping": {
      "address": {
        "city": "Random City",
        "country": "NZ",
        "line1": "1600 Random Address",
        "line2": "",
        "postal_code": "3015",
        "state": "WGN"
      },
      "carrier": null,
      "name": "Card Holder Name",
      "phone": null,
      "tracking_number": null
    },
    "source": null,
    "source_transfer": null,
    "statement_descriptor": null,
    "statement_descriptor_suffix": null,
    "status": "succeeded",
    "transfer_data": null,
    "transfer_group": null
  },
  "previous_attributes": {
    "amount_refunded": 0,
    "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xT1V3YjZHd0VFdzR0angwKJaC3boGMgbEybNpVgY6LBZcFYNsXB29FB4pNUqr2NQBA7Gxab6MuD-JSipUrblK1LSLg2VlVdB4RIs5",
    "refunded": false
  }
}
(change `object.id` property to match your charge ID. You can grab it on your Stripe dashboard or on the order edit page)

--- Request the webhook endpoint on your store using the event body (/?wc-api=wc_stripe) using Postman or something similar

  • Confirm the handler was called: order status should be changed to failed and the following note added:
    "This payment has expired. Order status changed from On hold to Failed."

  • Covered with tests (or have a good reason not to test in description ☝️)
  • Added changelog entry in both changelog.txt and readme.txt (or does not apply)
  • Tested on mobile (or does not apply)

Post merge

@wjrosa wjrosa self-assigned this Dec 10, 2024
@wjrosa wjrosa marked this pull request as ready for review December 11, 2024 11:59
@wjrosa wjrosa requested review from a team and mattallan and removed request for a team December 11, 2024 16:10
Copy link
Contributor

@mattallan mattallan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @wjrosa for implementing this!

Great suggestion with using postman to test this. Unfortunately I didn't have much luck with this but I think I was building the request incorrectly. The code in this PR is straightforward and looks good so I'm happy to approve it.

I just left a small comment about changelog and readme still pointing to 9.0 but I don't think these changes will go out in that release.

changelog.txt Show resolved Hide resolved
@wjrosa wjrosa merged commit 1977aff into develop Dec 12, 2024
33 of 35 checks passed
@wjrosa wjrosa deleted the add/handling-charge-expiration-webhooks branch December 12, 2024 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants