Maybe the component should be User Interface for this one, and maybe it should in Commerce project, not sure.
As a user with appropriate permissions, I can click "Refund" at admin/commerce/orders/{order_id}/payments for payments even if they have not been settled. This results in an error from Authorize.net because un-settled transactions can only be voided, not refunded.
Possible solutions?
- Use the API to get the settlement status & implement a voidPayment method
- Hide the refund button for 24 hours (yuck)
- SupportsVoidInterface?
We could also consider providing helpful messaging instead of a code fix, but the error from Authorize.net is less than helpful: E000027 The transaction was unsuccessful
. We'd be making an educated guess as to why.
Issue fork commerce_authnet-2911837
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
mglamanI wonder if there is some logic governing the amount of time before a refund is valid. Apparently I'm testing it:
https://github.com/commerceguys/authnet/blob/master/tests/CreateTransact...
Comment #3
mglamanI wonder if on E000027 we should try a void on the transaction. If that fails then we relay the original error?
Comment #4
mglamanAbandoning this for now.
If we try to void, there is no "partial" void. See https://developer.authorize.net/api/reference/#payment-transactions-void...
The best bet is to get a detailed log of the response that Authorize.net sends when the transaction is not yet settled. We'll want to show a message that says it is not yet settled, probably.
Comment #5
mglamanPostponing on needing a response where the transaction has no yet settled. Once we have that, let's plan an attack.
Comment #6
freality CreditAttribution: freality commentedI ran into the issue on the system I'm currently building.
You can resolve it by fetching the transaction details from Auth.net. Then using the transaction status to override
PaymentGatewayBase::buildPaymentOperations
.The attached patch adds this logic.
Please review as I'm not all too sure I understand the semantics of Authorize.net's transactionStatus.
More info on the API call: https://developer.authorize.net/api/reference/index.html#transaction-rep...
Comment #7
freality CreditAttribution: freality commentedSorry. Forgot patch.
Comment #8
freality CreditAttribution: freality commentedI forgot to mention that the Transaction Details API has to be enabled in your Authorize.net console settings. That being true, I think it would be necessary to inform the user to this in the Payment Method configuration form. An opt-in checkbox maybe?
Comment #9
mglamanI almost wonder if we should always show the link and just perform the status check on the form. That's a lot of individual API calls if someone made a transaction view.
Comment #10
freality CreditAttribution: freality commentedI think presenting an option to users which is not available would be a source of confusion. A user would select the Refund link then be presented with a message stating that While we thought we could preform a refund at this time, apparently we can't. But you can try this option again sometime in the future, and maybe will be able to then ;). Or at worse the bewildering Transaction unsuccessful; as is now.
You could reduce the number of calls to the API by saving transactionStatus to $payment->setRemoteState(). Therefore, you'd only be calling the api for a subset of statuses; like
if lastKnownRemoteState() is a pending_state, refreshRemoteStatus. Otherwise, useLocalRemoteStatus
.Comment #11
mglamanThat makes sense. And I do agree - having a valid action is better than not. And the edge case is if someone made a view. Otherwise there'd only be one call.
Comment #12
freality CreditAttribution: freality commentedThe attached patch implements the discussed caching of remote statuses. Including adding a configuration option for enabling the extended transaction details API.
Please review and comment.
P.S. The previous patch #7 does not work IRL. It's missing use statements which breaks namespacing.
Comment #14
mglamanSo, I really wish we didn't need this flag. And unfortunately the merchant details request doesn't give us that information either: https://developer.authorize.net/api/reference/index.html#transaction-rep...
In #2995922: Echeck payment states we're looking into batches. I wonder if we could do that somehow.
https://developer.authorize.net/api/reference/index.html#transaction-rep...
Comment #15
bojanz CreditAttribution: bojanz at Centarro commentedIt definitely feels wrong to an API call every time a payment is listed.
I'd go with the validation in the form + code that updates the status on cron (do we need a CronInterface for payment gateways?)
Comment #16
freality CreditAttribution: freality commented@mglaman I agree that "just works" solutions are better than configuration. But since authorize.net's Transaction Details API in an opt-in feature, I think you have to disclose it as a requirement for proper use of the module; or allow user's the option to opt-out. Otherwise, you must have a strategy to auto-detect whether the feature is enabled. The API returns Error responses when that API is not enabled.
I'm not aware of the development going on with Echecks, but I'm willing to code this issue to resolution in any solution you feel best.
@bojanz The strategy to cache the remote status means you'd only be calling the API on pending-type transactions. And while the need to pester the API with update requests is funky, it's certainly better than having users attempt operations which fail with nondescript error messages.
I think a more robust solution would be to utilize Authorize.net's webhook API (https://developer.authorize.net/api/reference/features/webhooks.html). Although, there doesn't appear to be a hook for transaction settled; which is disappointing.
Comment #17
Nathaniel CreditAttribution: Nathaniel commentedRe-rolled. Some minor comment/code cleanup. I had to add 'completed' to OnsiteBase voidPayment allowed states.
Seems to be working as expected. @freality thanks for the patch!
Comment #19
Nathaniel CreditAttribution: Nathaniel commentedUpdated schema. Added default value. Work on coding standards.
Comment #21
Nathaniel CreditAttribution: Nathaniel commentedWork on existing tests. Added
'use_transaction_details_api' => TRUE,
to testPreAcceptJsUpgrade.Comment #23
Nathaniel CreditAttribution: Nathaniel commentedComment #24
shawngo CreditAttribution: shawngo commentedI just ran into this issue and was searching for a way to find out if the transaction has been settled. I'm working on an integration with an ERP system which sends shipment information back to the Drupal site which then performs the transaction capture/refund/void/etc.
I couldn't find the Transaction Details API setting so I emailed Authorize.net support. I was informed that "[the] Transaction Details API is automatically enabled on all accounts now, there is no longer a setting to turn it on."
Comment #25
rhovland CreditAttribution: rhovland commentedRe-rolled patch so it applies cleanly to dev. Removed the configuration and checks since the API is always enabled on all accounts now.
I tested it on our account which is 10+ years old and it worked perfectly without needing to mess with account settings.
Comment #26
andyg5000Thanks for the re-roll Ryan. This is working well, but commerce core doesn't support the balance recalculation properly after a void (see #3356936: Order balance calculation discrepancy for voided and expired payments.).
I've updated the patch to include a forced refresh of the balance. This won't break anything with related to a fix for 3356936, but should be removed once that's resolved.
Comment #27
rhovland CreditAttribution: rhovland commentedSo there's an issue if you're using authorize and capture. The void button is shown on settled transactions if you previously viewed it when it was unsettled. It's because of the section of code where it checks if it should refresh the status or not is missing
capturedPendingSettlement
as one of the status codes.Attached a patch that fixes this problem.
Comment #28
rhovland CreditAttribution: rhovland commentedHere is an updated patch to account for changes in the latest release.
Switched commerce_payment.order_updater to use dependency injection
Fixed a couple minor errors in the comments
Comment #30
rhovland CreditAttribution: rhovland commentedComment #31
rhovland CreditAttribution: rhovland commentedI screwed up dependency injection in the patch. Here's the correct version