Duplicate Transactions and x_duplicate_window

659 views
Skip to first unread message

Matt Todd

unread,
Nov 17, 2008, 11:12:46 AM11/17/08
to Active Merchant
Doing a quick scan I didn't see any specific documentation for
x_duplicate_window settings for Authorize.net and suppressing the
"Duplicate transaction" error from happening, I assume this is just an
option I supply when calling:

ActiveMerchant::Billing::AuthorizeNetGateway.new(credentials,
{:x_duplicate_window => window_value})

Can you clarify this is how it should be set? I'd seen some emails on
the list from April 2007 about this but didn't see anything else
afterwards. Your input would be appreciated.

More specifically, though, what is a more generally sensible (or
standard) way to handle duplicate transactions since this error can
occur when it should've been considered a success... Just looking for
opinion here.

Matt

Alex Lebedev

unread,
Nov 19, 2008, 12:52:32 PM11/19/08
to Active Merchant
I've faced this problem about 10 months ago.

If you just supply x_duplicate_window as an extra option, it gets
cleaned up in process. I solved this by modifying commit method and
hardcoding desired value there. The code is pasted below

***

require 'active_merchant'
module ActiveMerchant
module Billing
class AuthorizeNetGateway
private

def commit_with_duplicate_window(action, money, parameters)
# Transaction with the same card and amount as one commited
within duplicate window is rejected by Authorize.net
parameters[:duplicate_window] = 1 # 1 second instead of
default 2 minutes.
commit_without_duplicate_window(action, money, parameters)
end

alias_method_chain :commit, :duplicate_window
end
end
end

Mark

unread,
Nov 19, 2008, 1:35:35 PM11/19/08
to Active Merchant
Hi Matt,
Have you been able to change your duplicate window? I had an
authorize.net support person tell me that it can't really be done,
and I had another one point me here:
http://www.authorize.net/kb.asp?page_id=169764&url=/authorize.net/consumer/kbdetail.asp%3Fkbid%3D381

If anyone has been able to do this successfully I would like to see
how they did it.
Also I believe you want to leave the x_ off when you supply those
params and the gateway class adds it on again before sending.

- Mark

Mark Maggelet

unread,
Nov 19, 2008, 3:29:06 PM11/19/08
to activem...@googlegroups.com
Thanks Alex, that helped me out a lot.

Cody Fauser

unread,
Nov 19, 2008, 4:28:13 PM11/19/08
to activem...@googlegroups.com
duplicate_window would be good as a cattr_accessor on the AuthorizeNet
gateway class. By default it would be the Authorize.net default, but
it could then be configured. I would accept a patch for this.
--
Cody Fauser
http://shopify.com - e-commerce done right
http://www.codyfauser.com - blog
http://peepcode.com/products/activemerchant-pdf - ActiveMerchant PeepCode
http://www.oreilly.com/catalog/rjsrails - RJS Templates for Rails

Seamus Abshere

unread,
Dec 30, 2008, 3:23:40 PM12/30/08
to Active Merchant
Cody,

Here's a patch that lets you set x_duplicate_window like this:

Gateway = ActiveMerchant::Billing::Base.gateway(:authorize_net).new(
:login => AUTHORIZE_NET_LOGIN,
:password => AUTHORIZE_NET_PASSWORD,
:duplicate_window => 0
)

This made sense in the context of my application, but I believe it is
not the cattr_accessor approach that you specified. Please let me know
if you would like me to re-implement this in a different way and I
will submit a new patch.

Best,
Seamus

*********************

From a92cc8d9a58c38da456e24b59f30dbe47960dae1 Mon Sep 17 00:00:00 2001
From: Seamus Abshere <sea...@abshere.net>
Date: Tue, 30 Dec 2008 15:15:57 -0500
Subject: [PATCH] Add duplicate_window as a Gateway initialization
option for Authorize.Net

---
.../billing/gateways/authorize_net.rb | 15 ++++++++++++
+++
test/unit/gateways/authorize_net_test.rb | 17 ++++++++++++
+++++
2 files changed, 32 insertions(+), 0 deletions(-)

diff --git a/lib/active_merchant/billing/gateways/authorize_net.rb b/
lib/active_merchant/billing/gateways/authorize_net.rb
index 2ae4ebd..c9cb1b3 100644
--- a/lib/active_merchant/billing/gateways/authorize_net.rb
+++ b/lib/active_merchant/billing/gateways/authorize_net.rb
@@ -66,6 +66,10 @@ module ActiveMerchant #:nodoc:
# * <tt>:password</tt> -- The Authorize.Net Transaction Key.
(REQUIRED)
# * <tt>:test</tt> -- +true+ or +false+. If true, perform
transactions against the test server.
# Otherwise, perform transactions against the production
server.
+ # * <tt>:duplicate_window</tt> -- Indicates in seconds the
window of time after a transaction is
+ # submitted during which the payment gateway will check for a
duplicate transaction. The maximum
+ # time allowed is 8 hours (28,800 seconds). Default is 120
seconds. If this option is set,
+ # an enhanced duplicate transaction response will be sent.
def initialize(options = {})
requires!(options, :login, :password)
@options = options
@@ -86,6 +90,7 @@ module ActiveMerchant #:nodoc:
add_creditcard(post, creditcard)
add_address(post, options)
add_customer_data(post, options)
+ add_duplicate_window(post)

commit('AUTH_ONLY', money, post)
end
@@ -103,6 +108,7 @@ module ActiveMerchant #:nodoc:
add_creditcard(post, creditcard)
add_address(post, options)
add_customer_data(post, options)
+ add_duplicate_window(post)

commit('AUTH_CAPTURE', money, post)
end
@@ -332,6 +338,15 @@ module ActiveMerchant #:nodoc:
post[:state] = address[:state].blank? ? 'n/a' : address
[:state]
end
end
+
+ # x_duplicate_window won't be sent by default, because sending
it changes the response.
+ # "If this field is present in the request with or without a
value, an enhanced duplicate transaction response will be sent."
+ # (as of 2008-12-30) http://www.authorize.net/support/AIM_guide_SCC.pdf
+ def add_duplicate_window(post)
+ if @options.has_key? :duplicate_window
+ post[:duplicate_window] = @options[:duplicate_window]
+ end
+ end

# Make a ruby type out of the response string
def normalize(field)
diff --git a/test/unit/gateways/authorize_net_test.rb b/test/unit/
gateways/authorize_net_test.rb
index 0b6a81b..d6f4f8f 100644
--- a/test/unit/gateways/authorize_net_test.rb
+++ b/test/unit/gateways/authorize_net_test.rb
@@ -73,6 +73,23 @@ class AuthorizeNetTest < Test::Unit::TestCase
assert_equal 'My Purchase is great', result[:description]
end

+ def test_add_duplicate_window_without_duplicate_window
+ result = {}
+ @gateway.send(:add_duplicate_window, result)
+ assert_nil result[:duplicate_window]
+ end
+
+ def test_add_duplicate_window_with_duplicate_window
+ gateway = AuthorizeNetGateway.new(
+ :login => 'X',
+ :password => 'Y',
+ :duplicate_window => 0
+ )
+ result = {}
+ gateway.send(:add_duplicate_window, result)
+ assert_equal 0, result[:duplicate_window]
+ end
+
def test_purchase_is_valid_csv
params = { :amount => '1.01' }

--
1.6.0.2

Cody Fauser

unread,
Dec 31, 2008, 10:43:36 AM12/31/08
to activem...@googlegroups.com
Seamus,

Thanks for the patch. However, I think this makes more sense as a
cattr_accessor because I don't think there is a need to customize the
duplicate window on an instance by instance basis.

Seamus Abshere

unread,
Dec 31, 2008, 10:52:16 AM12/31/08
to Active Merchant
Cody,

Would you give me a usage example? I looked around in the code
yesterday for an example of a comparable option and I have to admit I
didn't find anything.

Thanks,
Seamus
> > +      # (as of 2008-12-30)http://www.authorize.net/support/AIM_guide_SCC.pdf

Cody Fauser

unread,
Dec 31, 2008, 12:53:59 PM12/31/08
to activem...@googlegroups.com
Seamus,

I think the code would be something like:

class AuthorizeNetGateway < Gateway
cattr_accessor :duplicate_window
self.duplicate_window = 30 # Or whatever the default with Authorize.net is

# cattr_accessor defines an instance reader by default
def add_duplicate_window(post)
post[:duplicate_window] = duplicate_window
end
end

Then if you wanted to override the default in your application, such
as config/environment.rb in Rails:

ActiveMerchant::Billing::AuthorizeNetGateway.duplicate_window = 60

This way you'd only have to make the change once in an entire application.

Seamus Abshere

unread,
Dec 31, 2008, 12:58:04 PM12/31/08
to Active Merchant
Cody,

By the way, I am having a hard time understanding the semantic
difference between a cattr_accessor and what I did. I think you mean
something like

class_inheritable_accessor :duplicate_window
self.duplicate_window = 120

In which case I would have to do something like this in my application
initializers:

Gateway = ActiveMerchant::Billing::Base.gateway(:authorize_net).new(
:login => AUTHORIZE_NET_LOGIN,
:password => AUTHORIZE_NET_PASSWORD,
)
Gateway.duplicate_window = 0

What's the idea?

Best,
Seamus

PS. This is how I use "Gateway" in my ActiveRecord models:

def purchase!
Gateway.purchase(amount, @creditcard)
end

Matt Todd

unread,
Dec 31, 2008, 1:14:11 PM12/31/08
to activem...@googlegroups.com
Sorry for not getting back sooner... email just got lost in the deluge of work emails.

The way we handled this problem was to determine if they'd had a successful authorization in the past 2 minutes (or whatever the duplicate window timeout was set to) and if so, and this was the error they, got, then we reuse the authorization from last transaction and assume that it was successful.

However, if no previous authorization exists (within the window), we treat it like a generic error.

Matt
--
Matt Todd
Highgroove Studios
www.highgroove.com
cell: 404-314-2612
blog: maraby.org

Scout - Web Monitoring and Reporting Software
www.scoutapp.com

Cody Fauser

unread,
Dec 31, 2008, 1:50:10 PM12/31/08
to activem...@googlegroups.com
Matt,

Do you have the duplicate window as a global setting in your app, or
do you need to change it between requests?

Seamus Abshere

unread,
Dec 31, 2008, 2:11:28 PM12/31/08
to Active Merchant
(a) If Matt says it's a global setting for his app, I will re-
implement according to Cody's original idea, with a cattr_accessor
(i.e. class_inheritable_accessor, I think).

(b) If Matt says he uses it on a case-by-case basis, I will re-
implement by adding add_duplicate_window(post, options) to

AuthorizeNetGateway#authorize
AuthorizeNetGateway#purchase

Matt, in case of (b), do you need it on any other methods, say for
example AuthorizeNetGateway#capture?

Matt Todd

unread,
Dec 31, 2008, 4:06:46 PM12/31/08
to activem...@googlegroups.com
It's an application-wide constant currently...

AppConstants::DUPLICATE_WINDOW = 2.minutes

Then the logic in our controller tests for authorization failure && error message matches /duplicate/ && recent authorization is not nil.

Hope that elucidates our process.

Matt

Matt Todd

unread,
Dec 31, 2008, 4:07:27 PM12/31/08
to activem...@googlegroups.com
That is to say, we also check the authorization timestamp and if it's less than the constant, we evaluate the other conditions as well.

Matt

Seamus Abshere

unread,
Jan 1, 2009, 5:14:12 PM1/1/09
to Active Merchant
Cody and Matt,

Here's the patch:

http://pastie.org/350222

You can set x_duplicate_window with

ActiveMerchant::Billing::AuthorizeNetGateway.duplicate_window = 60

The default is 120 seconds. If you set it to 0, then it won't do any
duplicate checking at all---in other words, it loosens the criteria.

Seamus

Seamus Abshere

unread,
Jan 1, 2009, 5:41:50 PM1/1/09
to Active Merchant
Here's a better test for not sending x_duplicate_window in the post:

http://pastie.org/350227

Explanation:

Unless the programmer sets x_duplicate_window to a non-nil value, I
don't send it in the post. That's because I don't want everybody who
uses the AuthorizeNetGateway to suddenly start getting "enhanced
responses":

"If this field is present in the request with or without a value, an
enhanced duplicate transaction response will be sent."
http://www.authorize.net/support/AIM_guide_SCC.pdf

Since all I do is a simple check for nil, there is a caveat for the
(likely few) people who want the "enhanced response" without changing
x_duplicate_window. You have to do this:

ActiveMerchant::Billing::AuthorizeNetGateway.duplicate_window = 120

or whatever the current Authorize.Net default is. Please let me know
if this becomes a significant annoyance for anybody.

Matt Todd

unread,
Jan 2, 2009, 11:54:06 AM1/2/09
to activem...@googlegroups.com
Patch looks good to me.

We will continue to just test the criteria in our application of the failure, but in the future we will probably make use of setting the duplicate window value.

Thanks guys,

Matt

Seamus Abshere

unread,
Jan 7, 2009, 4:23:32 PM1/7/09
to Active Merchant
Cody,

Would you provide an ETA on accepting/rejecting this patch? Otherwise,
I'll just fork on Github and use this code in the meantime.

Thanks,
Seamus

Cody Fauser

unread,
Jan 7, 2009, 9:53:11 PM1/7/09
to activem...@googlegroups.com
Seamus,

Patch applied. Good job.
Reply all
Reply to author
Forward
0 new messages