active_merchant
active_merchant copied to clipboard
Authnet: accept.js support
Authorize.net provides a way to capture credit cards without credit card data (number, expiration, etc) ever passing through the merchant's server. Using their accept.js client library, CC info is submitted directly to their server, which responds with a payment nonce. This nonce can then be sent safely though the merchant's server and used in authorize and purchase transactions. Full documentation here: https://developer.authorize.net/api/reference/features/acceptjs.html
This PR adds an OpaqueDataPaymentToken to handle authnet's payment nonce.
Given the following output from the API called made by the accept.js library:
{
"opaqueData": {
"dataDescriptor": "COMMON.ACCEPT.INAPP.PAYMENT",
"dataValue": "eyJj...MSJ9"
},
"messages": {
"resultCode": "Ok",
"message": [{"code": "I00001", "text": "Successful."}]
}
}
Authorization can be submitted like this:
token = OpaqueDataPaymentToken.new('eyJj...MSJ9', data_descriptor: 'COMMON.ACCEPT.INAPP.PAYMENT')
gateway = AuthorizeNetGateway.new(login: '', password: '')
gateway.authorize(123, token, order_id: 1)
Resolves https://github.com/activemerchant/active_merchant/issues/3187
% ruby -I test test/remote/gateways/remote_authorize_net_test.rb
Loaded suite test/remote/gateways/remote_authorize_net_test
Started
.....................................................................
Finished in 54.372302 seconds.
------------------------------------------------------------------------------------------------------------------------
69 tests, 238 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
------------------------------------------------------------------------------------------------------------------------
1.27 tests/s, 4.38 assertions/s
% ruby -I test test/remote/gateways/remote_authorize_net_opaque_data_test.rb
Loaded suite test/remote/gateways/remote_authorize_net_opaque_data_test
Started
.....
Finished in 6.58923 seconds.
------------------------------------------------------------------------------------------------------------------------
5 tests, 19 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
------------------------------------------------------------------------------------------------------------------------
0.76 tests/s, 2.88 assertions/s
Loaded suite test/unit/gateways/authorize_net_test
Started
.................................................................................................
Finished in 0.251408 seconds.
------------------------------------------------------------------------------------------------------------------------
97 tests, 575 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
------------------------------------------------------------------------------------------------------------------------
385.83 tests/s, 2287.12 assertions/s
Hi @dingels35, still looking at this, but my first thought is whether the OpaqueDataPaymentToken
the best name/abstraction. The name is a bit general; is the class also general enough to be used for similar implementations, or are there specifics that tie it to Authnet Accept.js's notion of a payment token?
@curiousepic - Good question. I definitely wanted this to be generic enough to handle other (possibly future) payment types.
There is an existing ApplePayPaymentToken that generates the exact same xml - <opaqueData><dataDescriptor>...</dataDescriptor><dataValue>...</dataValue></opaqueData>
. I considered creating an OpaqueDataPaymentToken as a base for both an ApplePayPaymentToken and an AcceptJsPaymentToken, but I didn't really want to mess with existing apple pay functionality.
The way I have things set now, this could be used for apple pay scenarios. It can also be used for Visa Checkout and Android Pay (documentation links below). So I think having just one OpaqueData type that can be used for all 4 scenarios (accept.js, apple, android, visapay) is a good way to build support for multiple current, and possible future, authorize.net payment options.
https://developer.authorize.net/api/reference/index.html#visa-checkout https://developer.authorize.net/api/reference/index.html#mobile-in-app-transactions-create-an-android-pay-transaction
@dingels35 I was actually thinking more about applications beyond Authnet, rather than other Authnet token types. If it's tied to Authnet, let's maybe make that explicit in the name.
@curiousepic - I like your thoughts. We could definitely add token classes for other payment types - AndroidPayPaymentToken, VisaCheckoutPaymentToken, and AcceptJsPaymentToken. Doing this would mostly require that each merchant consider each class separately.
Another option would be to allow the existing PaymentToken act on its own. I think I could use the same code listed in my PR summary to use a PaymentToken instead of an OpaqueDataPaymentToken. I'd likely just need to modify the PaymentToken class to something like this:
class PaymentToken
attr_reader :payment_data, :metadata
def initialize(payment_data, options = {})
@payment_data = payment_data
@metadata = options.with_indifferent_access
end
def type
'payment_token'
end
end
Exposing metadata
would allow me to pull out the data_descriptor in gateways/authorize_net.rb. And I could also validate in that class that token.metadata[:data_descriptor]
exists.
Thoughts on this approach? If you're not keen on it, I can also just rename that class to AuthorizeNetPaymentToken
and keep this scoped to authnet only.
@dingels35 Hi Cory, these are some interesting approaches. I think we should probably keep things slim here since we're approaching scope bloat, but I'd like to get some more perspectives for how to proceed from my colleagues and get back to you. @activemerchant/shopify-payments-devs may be interested as well.
@bayprogrammer Nice, I like that path as well.
what was wrong with https://github.com/activemerchant/active_merchant/pull/2422 ?
what was wrong with #2422 ?
@taf2 I'm not aware that anything was necessarily wrong with it, but I'm guessing it simply didn't get picked up on our radar at the time. We've been making a renewed push at becoming more proactive in responding to issues and PRs.
@dingels35 How are things going on your side? Were my recommended changes something you can do?
@bayprogrammer - Thanks for checking back in. Your comments are good - I like that approach. I had some time off earlier in June and am heading out for a long weekend again. But I'll get this updated for you to look at in the next 2 weeks.
@dingels35 Awesome, thanks so much. Have a great long weekend! 😄
Hi all 👋 Would love to get this merged so we can all drop our Accept.js monkeypatches. Any updates on it?
@dingels35 @dfltr I apologize I didn't see that this PR had been updated since I last looked at it. It's back on my radar and I will try to re-review and hopefully we can get this one merged in!
Hey guys, checking back in on this one: I'd love to use Accept.js via ActiveMerchant! :)
My patch still works...
@bayprogrammer or @curiousepic 👋 I'm on @dingels35 's team, revisiting this issue. We'd love to drop our fork of ActiveMerchant if we can get this (or https://github.com/activemerchant/active_merchant/pull/4406) across the line.
Last we saw I think we were waiting on comments from @bayprogrammer. Is it realistic to revive this PR at this point, or how can we proceed?
@ChrisNelsonBHG apologies, I am afraid I wasn't able to get back to this while I was still at Spreedly. I no longer work for Spreedly and I no longer have commit access to this project (nor would I remember enough about this accept.js business to be of much help any longer :sweat_smile:).
Marking this "of interest" before a cleanup of stale PRs
same: Marking this "of interest" before a cleanup of stale PRs
Pretty sure we forked active_merchant long ago for this specific feature in authnet - seems pretty important feature...