diff options
| author | tbe-odoo <tbe@odoo.com> | 2017-07-28 15:52:34 +0200 |
|---|---|---|
| committer | Damien Bouvy <dbo@odoo.com> | 2017-08-14 08:30:59 +0200 |
| commit | 2df9c22d80be82b4ed7ffcd79f858e46c34e6479 (patch) | |
| tree | 5f3d7d68c4fc4e461e1a3f235266b14c23d0340a /addons/payment_authorize | |
| parent | c089d39a668fa6ab1f5d7867747da19d0b75f035 (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.py | 26 | ||||
| -rw-r--r-- | addons/payment_authorize/models/authorize_request.py | 27 | ||||
| -rw-r--r-- | addons/payment_authorize/models/payment.py | 39 | ||||
| -rw-r--r-- | addons/payment_authorize/views/payment_authorize_templates.xml | 54 |
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="•••• •••• •••• •••• "/> - <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> |
