[IMP] website_(portal_sale|sale|quote), payment: add authorization payment flow steps

This allows using a modern e-commerce flow where amounts are first
authorized at checkout then captured when the picking is in fact
shipped instead of immediately capturing the amount.

To access this flow, a new selection value is available on the payment
acquirer: "Authorize the amount and confirm the SO on acquirer
confirmation". If this value is selected, s2s/form transactions should
only require authorization to the acquirer; the user can then
capture/void the payment manually from the Odoo backend transaction
form view by simply using buttons.
This commit is contained in:
Damien Bouvy
2016-09-01 15:58:41 +02:00
parent 406611967b
commit a2d249f1a4
7 changed files with 46 additions and 5 deletions
+28 -1
View File
@@ -71,6 +71,7 @@ class PaymentAcquirer(models.Model):
help="Make this payment acquirer available (Customer invoices, etc.)")
auto_confirm = fields.Selection([
('none', 'No automatic confirmation'),
('authorize', 'Authorize the amount and confirm the SO on acquirer confirmation'),
('confirm_so', 'Confirm the SO on acquirer confirmation'),
('generate_and_pay_invoice', 'On acquirer confirmation, confirm the SO, generate the invoice and pay it')],
string='Order Confirmation', default='confirm_so', required=True)
@@ -374,6 +375,7 @@ class PaymentTransaction(models.Model):
state = fields.Selection([
('draft', 'Draft'),
('pending', 'Pending'),
('authorized', 'Authorized'),
('done', 'Done'),
('error', 'Error'),
('cancel', 'Canceled')], 'Status',
@@ -558,15 +560,40 @@ class PaymentTransaction(models.Model):
if hasattr(self, custom_method_name):
return getattr(self, custom_method_name)(**kwargs)
@api.multi
def s2s_capture_transaction(self, **kwargs):
custom_method_name = '%s_s2s_capture_transaction' % self.acquirer_id.provider
if hasattr(self, custom_method_name):
return getattr(self, custom_method_name)(**kwargs)
@api.multi
def s2s_void_transaction(self, **kwargs):
custom_method_name = '%s_s2s_void_transaction' % self.acquirer_id.provider
if hasattr(self, custom_method_name):
return getattr(self, custom_method_name)(**kwargs)
@api.multi
def s2s_get_tx_status(self):
""" Get the tx status. """
invalid_param_method_name = '_%s_s2s_get_tx_status' % self.acquirer_id.provider
if hasattr(self, invalid_param_method_name):
return getattr(self, invalid_param_method_name)()
return True
@api.multi
def action_capture(self):
if any(self.mapped(lambda tx: tx.state != 'authorized')):
raise ValidationError('Only transactions in the Authorized status can be captured.')
for tx in self:
tx.s2s_capture_transaction()
@api.multi
def action_void(self):
if any(self.mapped(lambda tx: tx.state != 'authorized')):
raise ValidationError('Only transactions in the Authorized status can be voided.')
for tx in self:
tx.s2s_void_transaction()
class PaymentToken(models.Model):
_name = 'payment.token'
+2
View File
@@ -208,6 +208,8 @@
<field name="arch" type="xml">
<form string="Payment Transactions">
<header>
<button type="object" name="action_capture" states="authorized" string="Capture Transaction" class="oe_highlight"/>
<button type="object" name="action_void" states="authorized" string="Void Transaction"/>
<field name="state" widget="statusbar" clickable="True"/>
</header>
<sheet>
@@ -52,6 +52,9 @@ class PaymentTransaction(models.Model):
# verify SO/TX match, excluding tx.fees which are currently not included in SO
amount_matches = float_compare(tx.amount, tx.sale_order_id.amount_total, 2) == 0
if amount_matches:
if tx.state == 'authorized' and tx.acquirer_id.auto_confirm == 'authorize':
_logger.info('<%s> transaction authorized, auto-confirming order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id)
tx.sale_order_id.with_context(send_email=True).action_confirm()
if tx.state == 'done' and tx.acquirer_id.auto_confirm in ['confirm_so', 'generate_and_pay_invoice']:
_logger.info('<%s> transaction completed, auto-confirming order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id)
tx.sale_order_id.with_context(send_email=True).action_confirm()
@@ -271,6 +271,11 @@
Your transaction is waiting confirmation.
<span t-if='tx_post_msg' t-raw="tx_post_msg"/>
</div>
<div class="alert alert-warning alert-dismissable" t-if="tx_id and tx_state == 'authorized'">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
Your payment has been authorized.
<span t-if='tx_post_msg' t-raw="tx_post_msg"/>
</div>
<div class="alert alert-warning alert-dismissable" t-if="message==4 and quotation.state != 'sent'">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
This order
+3 -2
View File
@@ -748,7 +748,8 @@ class WebsiteSale(http.Controller):
tx.partner_id == tx.sale_order_id.partner_id):
try:
s2s_result = tx.s2s_do_transaction()
if not s2s_result or tx.state != 'done':
valid_state = 'authorized' if tx.acquirer_id.auto_confirm == 'authorize' else 'done'
if not s2s_result or tx.state != valid_state:
return dict(success=False, error=_("Payment transaction failed (%s)") % tx.state_message)
return dict(success=True, url='/shop/payment/validate')
except Exception, e:
@@ -884,7 +885,7 @@ class WebsiteSale(http.Controller):
if not order or (order.amount_total and not tx):
return request.redirect('/shop')
if (not order.amount_total and not tx) or tx.state in ['pending', 'done']:
if (not order.amount_total and not tx) or tx.state in ['pending', 'done', 'authorized']:
if (not order.amount_total and not tx):
# Orders are confirmed by payment transactions, but there is none for free orders,
# (e.g. free events), so confirm immediately
+1 -1
View File
@@ -8,7 +8,7 @@
<field name="arch" type="xml">
<xpath expr="//field[@name='amount_total']" position="after">
<field name="payment_acquirer_id" readonly="1" attrs="{'invisible': [('payment_acquirer_id', '=', False)]}" options='{"no_open":True}'/>
<field name="payment_tx_id" readonly="1" attrs="{'invisible': [('payment_acquirer_id', '=', False)]}" groups="base.group_no_one"/>
<field name="payment_tx_id" readonly="1" attrs="{'invisible': [('payment_acquirer_id', '=', False)]}"/>
</xpath>
</field>
</record>
+4 -1
View File
@@ -1535,6 +1535,9 @@
<t t-if="tx_ids and state == 'done'">
<p>Your payment has been received.</p>
</t>
<t t-if="tx_ids and state == 'authorized'">
<p>Your payment has been authorized.</p>
</t>
<t t-if="tx_ids and state == 'cancel'">
<p>The payment seems to have been canceled.</p>
</t>
@@ -1547,7 +1550,7 @@
</div>
</t>
</t>
<t t-if="tx_ids and state not in ['done', 'cancel', 'pending']">
<t t-if="tx_ids and state not in ['done', 'cancel', 'pending', 'authorized']">
<p>Your transaction is waiting confirmation.</p>
</t>
</template>