active_merchant icon indicating copy to clipboard operation
active_merchant copied to clipboard

Authnet: accept.js support

Open dingels35 opened this issue 5 years ago • 19 comments

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

dingels35 avatar May 16 '19 13:05 dingels35

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 avatar May 30 '19 14:05 curiousepic

@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 avatar May 30 '19 14:05 dingels35

@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 avatar May 30 '19 18:05 curiousepic

@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 avatar May 31 '19 02:05 dingels35

@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.

curiousepic avatar Jun 04 '19 17:06 curiousepic

@bayprogrammer Nice, I like that path as well.

curiousepic avatar Jun 04 '19 19:06 curiousepic

what was wrong with https://github.com/activemerchant/active_merchant/pull/2422 ?

taf2 avatar Jun 26 '19 16:06 taf2

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 avatar Jun 26 '19 20:06 bayprogrammer

@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 avatar Jun 27 '19 16:06 dingels35

@dingels35 Awesome, thanks so much. Have a great long weekend! 😄

bayprogrammer avatar Jun 27 '19 16:06 bayprogrammer

Hi all 👋 Would love to get this merged so we can all drop our Accept.js monkeypatches. Any updates on it?

dfltr avatar Oct 01 '19 00:10 dfltr

@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!

bayprogrammer avatar Oct 25 '19 12:10 bayprogrammer

Hey guys, checking back in on this one: I'd love to use Accept.js via ActiveMerchant! :)

paulsingh avatar Jul 29 '20 20:07 paulsingh

My patch still works...

taf2 avatar Jul 29 '20 20:07 taf2

@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 avatar Sep 27 '22 16:09 ChrisNelsonBHG

@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:).

bayprogrammer avatar Oct 05 '22 13:10 bayprogrammer

Marking this "of interest" before a cleanup of stale PRs

curiousepic avatar Oct 25 '23 17:10 curiousepic

same: Marking this "of interest" before a cleanup of stale PRs

Waylon87 avatar Apr 01 '24 21:04 Waylon87

Pretty sure we forked active_merchant long ago for this specific feature in authnet - seems pretty important feature...

taf2 avatar Apr 01 '24 21:04 taf2