woocommerce / woocommerce-gateway-stripe

The official Stripe Payment Gateway for WooCommerce
https://wordpress.org/plugins/woocommerce-gateway-stripe/
232 stars 204 forks source link

Partial refund data not reflected in order post meta or refund post meta #1073

Closed careyrob closed 9 months ago

careyrob commented 4 years ago

Affected ticket(s)

None

What I expected

I have Stripe set to Authorize when the customer places their order. The authorized credit card funds are not captured until I change the status to "Processing". This is because I need to remove sales tax for tax exempt customers.

When I remove the sales tax and process the transaction the authorized funds are captured and then Stripe automatically refunds the portion representing the sales tax. Removing the sales tax and updating the order also updates the '_order_tax' and '_order_total' in the order metadata.

The order post metadata related to the stripe transaction should also reflect the the partial refund of the sales tax. Since Stripe allocates part of the refund to their payout and part of it to their transaction fee this should be reflected accurately between the "Order Post Meta" and "Refund Post Meta" and should be reflected in the "stripe fee" and "stripe payout" fields within the wc-order-totals table.

I need for either the _stripe_net and _stripe_fee metadata to reflect the final values after the partial refund or for each component of the partial refund amount to be captured in the Refund Post Meta.

What happened instead

When I edit an order by removing the sales tax before processing the transaction it results in the authorized funds being captured and then the portion representing the sales tax is automatically refunded. Removing the Sales Tax and updating the order also updates the '_order_tax' and '_order_total' in the order metadata. All of this is functioning correctly.

The order metadata related to the stripe transaction does not reflect the the partial refund of the sales tax. The _stripe_net and _stripe_fee values reflect the values before the partial refund that automatically occurs after the funds are captured and nothing is recorded in the Refund Post Meta about the partial refund.

So, within the order metadata the _order_total reflects the sales tax refund, but the stripe related order metadata does not.

This is important because my invoicing solution uses these fields to determine when an order is paid-in-full. In the case of tax exempt customers it shows that no sales tax is included on their order and falsely indicates that they were over-charged by the amount of the sales tax which suggests that we owe them a refund.

Steps to reproduce the issue


v18 commented 4 years ago

Some dev notes for later: here's what I see when I edit the order to remove the tax, without refunding the order within WC:

removing-taxes-without-refunding-from-wc

And in Stripe:

stripe-payment-details

The result I got from the payment intents Stripe API call in capture_payment only had these values:

"amount": 4833,
"fee": 170,

So, we're not getting the same values from Stripe as Stripe displays in its dashboard. Any other useful values we could use to calculate the correct amounts are not sent (e.g. the stripe processing fee refund of $0.10).

v18 commented 4 years ago

Hi @careyrob, thanks for writing up your issue.

I am able to see a difference in the fees we show, but only when editing the order to remove the tax. To double-check that we're talking about the same process, could you please walk me step by step what you do in order to remove the tax for your tax-exempt customer? I'm not sure the steps I took at the same steps that you took, so I want to double-check.

If you could enumerate each step when removing the tax, and include a description and a screenshot, that would be the best.

This is important because my invoicing solution uses these fields to determine when an order is paid-in-full.

Can you tell me more which values your invoicing solution checks and from which places (e.g. Stripe and/or WooCommerce).

I wonder if there's anything we can help you with in the meanwhile - since we may not be able to fix this soon.

careyrob commented 4 years ago

The sales tax is assessed when the customer places the order through the shopping cart so it is included with the original order total and stripe payment intent.

I remove sales tax from qualified orders through the admin shop order page by editing the order item. image

I set the value to $0 in the sales tax "before discount" and "Total" fields. image image Then I save the order edits before changing the order status to "Pending" to capture the adjusted order total and refund the sales tax.

I've added my own code snippet to create an order balance field on my invoices. It uses data from the order post meta to calculate the order balance and accounts for payments from the woocommerce Stripe plugin, a plugin for manual stripe payments and entries for payments by cash/check.

Here is the code: `function wpo_wcpdf_templates_totals_zero_balance( $totals, $document_type, $document) { if ( $document_type == 'invoice' /&& $document->order->is_paid()/ ) { // set 'Paid' row $stripe_payment=get_post_meta( $document->order->id, '_stripe_net', true )+get_post_meta( $document->order->id, '_stripe_fee', true )+get_post_meta( $document->order->id, '_stripe_mp_total', true ); $check_payment = get_post_meta( $document->order->id, 'check_payment_amount', true ); $total_refunded = method_exists($document->order, 'get_total_refunded') ? $document->order->get_total_refunded() : 0; $previous_payments = -1*($stripe_payment+$check_payment+$total_refunded);
$ammont_due = get_post_meta( $document->order->id, '_order_total', true) + $previous_payments; $payment_rows = array( 'amount_paid' => array( 'label' => __( 'Payment Received:' ), 'value' => wc_price( $previous_payments , array( 'currency' => $document->order->get_currency() ) ), ), ); // set 'Amount due' row $due_rows = array( 'amount_due' => array( 'label' => __( 'Amount due:' ), 'value' => wc_price($ammont_due, array( 'currency' => $document->order->get_currency() ) ), ), ); // find array position of grand total line $grand_total_pos = 1; foreach ($totals as $key => $total_line) { if ($total_line['type'] == 'total' && $total_line['tax'] == 'incl') { // add amount due row $new_totals = array_slice( $totals, 0, $grand_total_pos, true )

While I'm waiting on the production fix for this bug could you provide a code snippet that adds the stripe data for refunds to the post meta when the charge is captured?

v18 commented 4 years ago

@careyrob thanks for the additional details.

I asked Stripe about this, and we'll update the issue when we hear back.

Your script uses the information available to WooCommerce, and the information WooCommerce gets incorrect because we get the wrong information from Stripe. Since we're working with data that's incorrect, the script can't be amended to figure out if the order was paid fully. Sorry about that.

I don't know how many orders you have where the tax is exempt. If it's not a lot, you can look up the correct Stripe amount paid by going to the Stripe page for that charge by clicking on the Stripe charge link from the order page:

stripe-charge-link

We also have an extension that can help with tax exempt orders here:

https://docs.woocommerce.com/document/tax-exempt-plugin/

Interal reference for this is: p1573922973049700-slack-woo-stripe-external

careyrob commented 4 years ago

Thank you for letting me know about the Tax Exempt plugin. I'll check it out.

The refund data is available in a few locations within the stripe response Body. Here's an example from one of my sales tax refunds: { "id": "{intent_id}", "object": "payment_intent", "amount": 20675, "amount_capturable": 0, "amount_received": 20300, "application": null, "application_fee_amount": null, "canceled_at": null, "cancellation_reason": null, "capture_method": "manual", "charges": { "object": "list", "data": [ { "id": "{charge_id}", "object": "charge", "amount": 20675, "amount_refunded": 375, ...... "receipt_url": "https://pay.stripe.com/receipts/{account_id}/{charge_id}/{receipt_id}", "refunded": false, "refunds": { "object": "list", "data": [ { "id": "{refund_id}", "object": "refund", "amount": 375, "balance_transaction": "{balance_transaction_id}", "charge": "{charge_id}", "created": {create_date}, "currency": "usd", "metadata": { }, "payment_intent": "{payment_intent_id}", "reason": null, "receipt_number": null, "source_transfer_reversal": null, "status": "succeeded", "transfer_reversal": null } ], ......

I think the first {"charges"/"data"/"amount_refunded"} is the possibly the current total refunded amount and the second {"charges"/"data"/"refunds"/"data"/"amount"} is particular to a specific refund issued against the initial charge. If more than one refund is issued against the total then there will be one {refund_id} for each refund within the {"charges"/"data"/"refunds"} portion of the response record.

Is the stripe response data not used to set the "_stripe_charge_captured" order meta when the charge capture succeeds? If so, couldn't you collect and post the refund amount data from the stripe response data and post it to the order meta at the same time?

(Incidentally, I would love to also capture the "receipt_url" from the stripe response data at the same time and save it in the order meta to streamline some of my business operations processes. It's a small change, but it would save me a significant amount of time by shortening a frequent and repetitive process.)

RadoslavGeorgiev commented 4 years ago

@v18 I'm moving this issue to 4.3.2, because I don't think we could fit it within the timeframe of 4.3.2.

I asked Stripe about this, and we'll update the issue when we hear back.

Do we have any updates? If yes, and this happens to be a quick fix, we might keep it in 4.3.2.

v18 commented 4 years ago

@RadoslavGeorgiev yes I heard back. We need to make an extra API request to get the correct totals. Not sure if it's quick.

allendav commented 4 years ago

@v18 - please add details here about the API request needed. We'll get to this one in 4.3.4

darcie commented 2 years ago

5425574-zen This store is using authorize and capture to edit line item totals based on stock, and the fee and net are not updating to reflect the amount captured.

salmaniz82 commented 2 years ago

Hi, I posted this issue from sam@webential.co.uk and I actually resolved this on my end on 29th July 2022. My solution is isolated from the plugin. Let me share my findings on it.

  1. Custom webbook endpoint when event = charge.capture then check if amount_refunded > 0 that means there is a partial refund.
  2. $balanceTransactionID = $event['data']['object']['refunds']['data'][0]['balance_transaction'];
  3. Making new request to Retrieve transaction refund via balanceTransactionId.
  4. Response has the adjusted amount for the fee.
  5. I used $wpdb to make DB updates without waiting for woocommerce to be initialized so that I can have updated relatively faster.

wpwoostripe_resolved

class-wc-stripe-webhook-handler.php process_webhook_capture() method need to make extra call.

I will try to implement my solution within the core and make a pull request, hopefully.

wjrosa commented 9 months ago

I have analyzed this issue in the last few days. I tried to reproduce it following the steps above several times and couldn't (it is working as expected for me). I believe the latest changes made by @ricardo here fixed the issue. The fees should be correctly updated now on partial refunds.