summaryrefslogtreecommitdiff
path: root/addons/payment_authorize
diff options
context:
space:
mode:
authortbe-odoo <tbe@odoo.com>2017-07-28 15:52:34 +0200
committerDamien Bouvy <dbo@odoo.com>2017-08-14 08:30:59 +0200
commit2df9c22d80be82b4ed7ffcd79f858e46c34e6479 (patch)
tree5f3d7d68c4fc4e461e1a3f235266b14c23d0340a /addons/payment_authorize
parentc089d39a668fa6ab1f5d7867747da19d0b75f035 (diff)
[IMP] Payments & subscriptions: Improved Payments
- When registering a payment token, validating it using a payment of a small amount (~1.50€) followed by a refund allows ensuring that the payment method is valid (i.e. checksumming the card number simple ensure the number is valid but not that the card exists). This commit introduces a generic approach that must be implemented for each acquirer that has tokenization support. This commit also introduces a generic payment token registration/usage template that can be adapted according to one's need. - Introducing a new payment form that handles payment, deletion and adding payment method (only for server2server for the moment). - On /my/payment_method, changed strings 'Payment Acquirers' to 'Payment Methods' which is more clear. - Stripe can now be used to pay subscriptions.
Diffstat (limited to 'addons/payment_authorize')
-rw-r--r--addons/payment_authorize/controllers/main.py26
-rw-r--r--addons/payment_authorize/models/authorize_request.py27
-rw-r--r--addons/payment_authorize/models/payment.py39
-rw-r--r--addons/payment_authorize/views/payment_authorize_templates.xml54
4 files changed, 105 insertions, 41 deletions
diff --git a/addons/payment_authorize/controllers/main.py b/addons/payment_authorize/controllers/main.py
index b42efe817bb..6b0aae9bf6a 100644
--- a/addons/payment_authorize/controllers/main.py
+++ b/addons/payment_authorize/controllers/main.py
@@ -35,7 +35,31 @@ class AuthorizeController(http.Controller):
def authorize_s2s_create_json(self, **kwargs):
acquirer_id = int(kwargs.get('acquirer_id'))
acquirer = request.env['payment.acquirer'].browse(acquirer_id)
- return acquirer.s2s_process(kwargs)
+ return acquirer.s2s_process(kwargs).id
+
+ @http.route(['/payment/authorize/s2s/create_json_3ds'], type='json', auth='public', csrf=False)
+ def authorize_s2s_create_json_3ds(self, verify_validity=False, **kwargs):
+ token = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))).s2s_process(kwargs)
+
+ if not token:
+ res = {
+ 'result': False,
+ }
+ return res
+
+ res = {
+ 'result': True,
+ 'id': token.id,
+ 'short_name': token.short_name,
+ '3d_secure': False,
+ 'verified': False,
+ }
+
+ if verify_validity != False:
+ token.validate()
+ res['verified'] = token.verified
+
+ return res
@http.route(['/payment/authorize/s2s/create'], type='http', auth='public')
def authorize_s2s_create(self, **post):
diff --git a/addons/payment_authorize/models/authorize_request.py b/addons/payment_authorize/models/authorize_request.py
index d8191dfc34c..fcfab1e3ac2 100644
--- a/addons/payment_authorize/models/authorize_request.py
+++ b/addons/payment_authorize/models/authorize_request.py
@@ -163,6 +163,33 @@ class AuthorizeAPI():
res['name'] = response_profile.find('paymentProfile/payment/creditCard/cardNumber').text
return res
+ def credit(self, token, amount, transaction_id):
+ """ Refund a payment for the given amount.
+
+ :param record token: the payment.token record that must be refunded.
+ :param str amount: transaction amount
+ :param str transaction_id: the reference of the transacation that is going to be refunded.
+
+ :return: a dict containing the response code, transaction id and transaction type
+ :rtype: dict
+ """
+ root = self._base_tree('createTransactionRequest')
+ tx = etree.SubElement(root, "transactionRequest")
+ etree.SubElement(tx, "transactionType").text = "refundTransaction"
+ etree.SubElement(tx, "amount").text = str(amount)
+ payment = etree.SubElement(tx, "payment")
+ credit_card = etree.SubElement(payment, "creditCard")
+ idx = token.name.find(' - ')
+ etree.SubElement(credit_card, "cardNumber").text = token.name[idx-4:idx] # shitty hack, but that's the only way to get the 4 last digits
+ etree.SubElement(credit_card, "expirationDate").text = "XXXX"
+ etree.SubElement(tx, "refTransId").text = transaction_id
+ response = self._authorize_request(root)
+ res = dict()
+ res['x_response_code'] = response.find('transactionResponse/responseCode').text
+ res['x_trans_id'] = transaction_id
+ res['x_type'] = 'refund'
+ return res
+
# Transaction management
def auth_and_capture(self, token, amount, reference):
"""Authorize and capture a payment for the given amount.
diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py
index 184f07cdc58..bfc5bfd569a 100644
--- a/addons/payment_authorize/models/payment.py
+++ b/addons/payment_authorize/models/payment.py
@@ -116,7 +116,7 @@ class PaymentAcquirerAuthorize(models.Model):
'partner_id': int(data.get('partner_id'))
}
PaymentMethod = self.env['payment.token'].sudo().create(values)
- return PaymentMethod.id
+ return PaymentMethod
@api.multi
def authorize_s2s_form_validate(self, data):
@@ -191,7 +191,7 @@ class TxAuthorize(models.Model):
@api.multi
def _authorize_form_validate(self, data):
- if self.state == 'done':
+ if self.state in ['done', 'refunded']:
_logger.warning('Authorize: trying to validate an already validated tx (ref %s)' % self.reference)
return True
status_code = int(data.get('x_response_code', '0'))
@@ -219,6 +219,9 @@ class TxAuthorize(models.Model):
'partner_id': self.partner_id.id,
})
self.payment_token_id = token_id
+
+ if self.payment_token_id:
+ self.payment_token_id.verified = True
return True
elif status_code == self._authorize_pending_tx_status:
self.write({
@@ -253,6 +256,17 @@ class TxAuthorize(models.Model):
return self._authorize_s2s_validate_tree(res)
@api.multi
+ def authorize_s2s_do_refund(self):
+ self.ensure_one()
+ transaction = AuthorizeAPI(self.acquirer_id)
+ self.state = 'refunding'
+ if self.type == 'validation':
+ res = transaction.void(self.acquirer_reference)
+ else:
+ res = transaction.credit(self.payment_token_id, self.amount, self.acquirer_reference)
+ return self._authorize_s2s_validate_tree(res)
+
+ @api.multi
def authorize_s2s_capture_transaction(self):
self.ensure_one()
transaction = AuthorizeAPI(self.acquirer_id)
@@ -272,8 +286,7 @@ class TxAuthorize(models.Model):
@api.multi
def _authorize_s2s_validate(self, tree):
- self.ensure_one()
- if self.state == 'done':
+ if self.state in ['done', 'refunded']:
_logger.warning('Authorize: trying to validate an already validated tx (ref %s)' % self.reference)
return True
status_code = int(tree.get('x_response_code', '0'))
@@ -287,6 +300,10 @@ class TxAuthorize(models.Model):
})
if init_state != 'authorized':
self.execute_callback()
+
+ if self.payment_token_id:
+ self.payment_token_id.verified = True
+
if tree.get('x_type').lower() == 'auth_only':
self.write({
'state': 'authorized',
@@ -294,13 +311,19 @@ class TxAuthorize(models.Model):
})
self.execute_callback()
if tree.get('x_type').lower() == 'void':
- self.write({
- 'state': 'cancel',
- })
+ if self.type == 'validation' and self.state == 'refunding':
+ self.write({
+ 'state': 'refunded',
+ })
+ else:
+ self.write({
+ 'state': 'cancel',
+ })
return True
elif status_code == self._authorize_pending_tx_status:
+ new_state = 'refunding' if self.state == 'refunding' else 'pending'
self.write({
- 'state': 'pending',
+ 'state': new_state,
'acquirer_reference': tree.get('x_trans_id'),
})
return True
diff --git a/addons/payment_authorize/views/payment_authorize_templates.xml b/addons/payment_authorize/views/payment_authorize_templates.xml
index abbbc80b93a..c4b91605a00 100644
--- a/addons/payment_authorize/views/payment_authorize_templates.xml
+++ b/addons/payment_authorize/views/payment_authorize_templates.xml
@@ -54,38 +54,28 @@
</template>
<template id="authorize_s2s_form">
- <form method="post" t-att-action="'/payment/authorize/s2s/create' if not json else '/payment/authorize/s2s/create_json'">
- <div t-att-class="'row' if bootstrap_formatting else ''">
- <div t-att-class="'form-group col-md-8' if bootstrap_formatting else 'form-group'">
- <label class="control-label" for="cc_number">Card number</label>
- <input type="tel" name="cc_number" id="cc_number" class="form-control" placeholder="&#8226;&#8226;&#8226;&#8226;&#32;&#8226;&#8226;&#8226;&#8226;&#32;&#8226;&#8226;&#8226;&#8226;&#32;&#8226;&#8226;&#8226;&#8226;&#32;"/>
- <div class="card_placeholder"></div>
- <div class="visa"></div>
- <input type="hidden" name="cc_brand" value=""/>
- </div>
- <div t-att-class="'form-group col-md-4' if bootstrap_formatting else 'form-group'">
- <label class="control-label" for="cc_cvc">CVC</label>
- <input type="text" name="cc_cvc" id="cc_cvc" class="form-control" maxlength="4" placeholder="CVC"/>
- </div>
- <div t-att-class="'form-group col-md-7' if bootstrap_formatting else 'form-group'">
- <label class="control-label" for="cc_holder_name">Name</label>
- <input type="text" name="cc_holder_name" id="cc_holder_name" class="form-control"/>
- </div>
- <div t-att-class="'form-group col-md-5' if bootstrap_formatting else 'form-group'">
- <label class="control-label" for="cc_expires_mm">Expires</label>
- <input type="text" name="cc_expiry" id="cc_expiry" class="form-control" maxlength="7" placeholder="MM / YY"/>
- </div>
- <input type="hidden" name="acquirer_id" t-att-value="id"/>
- <input class="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
- <input t-if="return_url" type="hidden" name="return_url" t-att-value="return_url"/>
- <input t-if="partner_id" type="hidden" name="partner_id" t-att-value="partner_id"/>
- <div class="clearfix"/>
+ <input type="hidden" name="data_set" data-create-route="/payment/authorize/s2s/create_json_3ds"/>
+ <div class="mt8">
+ <div t-att-class="'form-group col-md-12' if bootstrap_formatting else 'form-group'">
+ <input type="tel" name="cc_number" id="cc_number" class="form-control" placeholder="Card number"/>
+ <div class="card_placeholder"></div>
+ <div class="visa"></div>
+ <input type="hidden" name="cc_brand" value=""/>
</div>
- <button t-att-class="submit_class" class="btn btn-primary" type="submit">
- <span t-if="not submit_txt">Confirm <span class="fa fa-long-arrow-right"></span></span>
- <span t-if="submit_txt"><t t-esc="submit_txt"/></span>
- </button>
- </form>
- </template>
+ <div t-att-class="'form-group col-md-5' if bootstrap_formatting else 'form-group'">
+ <input type="text" name="cc_holder_name" id="cc_holder_name" class="form-control" placeholder="Cardholder name"/>
+ </div>
+ <div t-att-class="'form-group col-md-3' if bootstrap_formatting else 'form-group'">
+ <input type="text" name="cc_expiry" id="cc_expiry" class="form-control" maxlength="7" placeholder="Expires (MM / YY)"/>
+ </div>
+ <div t-att-class="'form-group col-md-4' if bootstrap_formatting else 'form-group'">
+ <input type="text" name="cc_cvc" id="cc_cvc" class="form-control" maxlength="4" placeholder="CVC"/>
+ </div>
+ </div>
+ <input type="hidden" name="acquirer_id" t-att-value="id"/>
+ <input class="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
+ <input t-if="return_url" type="hidden" name="return_url" t-att-value="return_url"/>
+ <input t-if="partner_id" type="hidden" name="partner_id" t-att-value="partner_id"/>
+ </template>
</data>
</odoo>