LasLabs has released a new Odoo plugin that allows transactions to be generated for Authorize.Net payments, subsequently reconciling the invoice that the payment is associated to.



The Problem

Odoo’s normal credit card workflow requires that a Payment Transaction be created before a customer can actually pay for the invoice. That screen then needs to be monitored in order to determine when you are paid (or you can monitor directly within your Authorize.Net account).

After the Authorize.Net transaction has been realized, a payment has to be created on the correct invoice, making sure to correctly reference the Authorize.Net transaction record to the invoice payment record.

Our Solution

We find this workflow to be clunky, and our customers often do not correctly link the actual transaction to the transaction that is being created in the Invoices screen (yes, they’re different records).

Our new plugin eliminates this manual interaction in favor of a more automatic approach. As soon as the customer is redirected back to your website after payment, a transaction record is created.

If the payment was approved, it is added automatically to the correct invoice with the correct references. After that, either a partial or full reconciliation will take place on the invoice depending on whether the full amount was received.

Sample Workflow

Here’s a sample of the workflow:

  1. Customer clicks Authorize.Net button in invoice or sale
    Screen Shot 2015-09-28 at 11.04.54 PM

  2. Customer fills out payment info, then hits Pay Now
    Authorize.Net Payment Screen

  3. If the payment is approved, you are paid and customer is redirected back to their account. If they paid the full invoice, they can see that it is paid. Otherwise, they can see/pay the remaining balance.
    Paid Invoice in tree view

  4. While looking at the invoice in the employee portal, you can see the new payments that were generated in the Payments tab
    Invoice Payments

The Downside

As with everything, there is always a downside.

If a transaction is declined after the initial approval, you will have to manually intervene by reversing a lot of accounting transactions.

This can be a pretty complicated process, but we have found it to be the lesser of two evils due to the rarity of this occurrence. If you are in a high risk market, this may be more of an issue.

We are open to community suggestions when it comes to improving this module (see Open Source) below. If you have an idea on how to circumvent this issue, please drop a comment or open an issue.

The process of reversing the reconciliation can be read here.

Download

As always – LasLabs is fully committed to Open Source, so the code is available from the following sources:

Features/Assistance

If you come across a bug, or require assistance/features – drop us a line in the comments, or on our Facebook!

Technical

The Error

Below is the error that this plugin is meant to solve:

2015-09-29 05:05:17,979 24363 INFO laslabs werkzeug: 127.0.0.1 - - [29/Sep/2015 05:05:17] "POST /payment/authorize/return/ HTTP/1.0" 500 -
2015-09-29 05:05:17,989 24363 ERROR laslabs werkzeug: Error on request:
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/werkzeug/serving.py", line 180, in run_wsgi
    execute(self.server.app)
  File "/usr/local/lib/python2.7/dist-packages/werkzeug/serving.py", line 168, in execute
    application_iter = app(environ, start_response)
  File "/usr/lib/python2.7/dist-packages/openerp/service/server.py", line 290, in app
    return self.app(e, s)
  File "/usr/lib/python2.7/dist-packages/openerp/service/wsgi_server.py", line 216, in application
    return application_unproxied(environ, start_response)
  File "/usr/lib/python2.7/dist-packages/openerp/service/wsgi_server.py", line 202, in application_unproxied
    result = handler(environ, start_response)
  File "/usr/lib/python2.7/dist-packages/openerp/http.py", line 1290, in __call__
    return self.dispatch(environ, start_response)
  File "/usr/lib/python2.7/dist-packages/openerp/http.py", line 1264, in __call__
    return self.app(environ, start_wrapped)
  File "/usr/local/lib/python2.7/dist-packages/werkzeug/wsgi.py", line 591, in __call__
    return self.app(environ, start_response)
  File "/usr/lib/python2.7/dist-packages/openerp/http.py", line 1264, in __call__
    return self.app(environ, start_wrapped)
  File "/usr/local/lib/python2.7/dist-packages/werkzeug/wsgi.py", line 591, in __call__
    return self.app(environ, start_response)
  File "/usr/lib/python2.7/dist-packages/openerp/http.py", line 1435, in dispatch
    result = ir_http._dispatch()
  File "/usr/lib/python2.7/dist-packages/openerp/addons/crm/ir_http.py", line 13, in _dispatch
    response = super(ir_http, self)._dispatch()
  File "/usr/lib/python2.7/dist-packages/openerp/addons/website/models/ir_http.py", line 148, in _dispatch
    resp = super(ir_http, self)._dispatch()
  File "/usr/lib/python2.7/dist-packages/openerp/addons/base/ir/ir_http.py", line 177, in _dispatch
    return self._handle_exception(e)
  File "/usr/lib/python2.7/dist-packages/openerp/addons/website/models/ir_http.py", line 196, in _handle_exception
    return super(ir_http, self)._handle_exception(exception)
  File "/usr/lib/python2.7/dist-packages/openerp/addons/base/ir/ir_http.py", line 147, in _handle_exception
    return request._handle_exception(exception)
  File "/usr/lib/python2.7/dist-packages/openerp/http.py", line 666, in _handle_exception
    return super(HttpRequest, self)._handle_exception(exception)
  File "/usr/lib/python2.7/dist-packages/openerp/addons/base/ir/ir_http.py", line 173, in _dispatch
    result = request.dispatch()
  File "/usr/lib/python2.7/dist-packages/openerp/http.py", line 684, in dispatch
    r = self._call_function(**self.params)
  File "/usr/lib/python2.7/dist-packages/openerp/http.py", line 310, in _call_function
    return checked_call(self.db, *args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/openerp/service/model.py", line 113, in wrapper
    return f(dbname, *args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/openerp/http.py", line 307, in checked_call
    return self.endpoint(*a, **kw)
  File "/usr/lib/python2.7/dist-packages/openerp/http.py", line 803, in __call__
    return self.method(*args, **kw)
  File "/usr/lib/python2.7/dist-packages/openerp/http.py", line 403, in response_wrap
    response = f(*args, **kw)
  File "/usr/lib/python2.7/dist-packages/openerp/addons/payment_authorize/controllers/main.py", line 24, in authorize_form_feedback
    request.env['payment.transaction'].sudo().form_feedback(post, 'authorize')
  File "/usr/lib/python2.7/dist-packages/openerp/api.py", line 239, in wrapper
    return new_api(self, *args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/openerp/api.py", line 463, in new_api
    result = method(self._model, cr, uid, *args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/openerp/addons/payment/models/payment_acquirer.py", line 440, in form_feedback
    tx = getattr(self, tx_find_method_name)(cr, uid, data, context=context)
  File "/usr/lib/python2.7/dist-packages/openerp/api.py", line 241, in wrapper
    return old_api(self, *args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/openerp/api.py", line 336, in old_api
    result = method(recs, *args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/openerp/addons/payment_authorize/models/authorize.py", line 115, in _authorize_form_get_tx_from_data
    raise ValidationError(error_msg)
ValidationError: Authorize: received data for reference SALE/2015/1234; no order found
0