akretion/payment-gateway

View on GitHub
payment_gateway_stripe/services/payment_service.py

Summary

Maintainability
A
25 mins
Test Coverage
# -*- coding: utf-8 -*-
# Copyright 2017 Akretion (http://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from openerp import models
from openerp.exceptions import Warning as UserError
from openerp.tools.translate import _
import json
import logging
_logger = logging.getLogger(__name__)

try:
    import stripe
except ImportError:
    _logger.debug('Can not import stripe')

MAP_SOURCE_STATE = {
    'canceled': 'cancel',
    'chargeable': 'to_capture',
    'consumed': 'succeeded',
    'failed': 'failed',
    'pending': 'pending',
    'succeeded': 'succeeded'}


class PaymentService(models.Model):
    _inherit = 'payment.service'
    _name = 'payment.service.stripe'
    _allowed_capture_method = ['immediately']

    def _get_error_message(self, code):
        return {
            'invalid_number':
                _("The card number is not a valid credit card number."),
            'invalid_expiry_month':
                _("The card's expiration month is invalid."),
            'invalid_expiry_year':
                _("The card's expiration year is invalid."),
            'invalid_cvc':
                _("The card's security code is invalid."),
            'invalid_swipe_data':
                _("The card's swipe data is invalid."),
            'incorrect_number':
                _("The card number is incorrect."),
            'expired_card':
                _("The card has expired."),
            'incorrect_cvc':
                _("The card's security code is incorrect."),
            'incorrect_zip':
                _("The card's zip code failed validation."),
            'card_declined':
                _("The card was declined."),
            'missing':
                _("There is no card on a customer that is being charged."),
            'processing_error':
                _("An error occurred while processing the card."),
        }[code]

    @property
    def _api_key(self):
        account = self._get_account()
        return account.get_password()

    def _prepare_charge(self, record, source=None, **kwargs):
        description = "%s|%s" % (
            record.name,
            record.partner_id.email)
        # For now capture is always true as only the policy 'immedialtely' is
        # available in the configuration but it will be easier to implement
        # the logic of defeared capture
        capture = record.payment_method_id.capture_payment == 'immediately'
        return {
            'currency': record.currency_id.name,
            'source': source,
            'description': description,
            'capture': capture,
            'amount': int(record.residual * 100),
            'api_key': self._api_key,
            }

    def _need_three_d_secure(self, record, source_data):
        return source_data['card']['three_d_secure'] != 'not_supported'

    def _prepare_source(self, record, source=None, return_url=None, **kwargs):
        return {
            'type': 'three_d_secure',
            'amount': int(record.residual * 100),
            'currency': record.currency_id.name,
            'three_d_secure': {'card': source},
            'redirect': {'return_url': return_url},
            'api_key': self._api_key,
        }

    def create_provider_transaction(self, record, source=None, **kwargs):
        source_data = stripe.Source.retrieve(source, api_key=self._api_key)
        three_d_secure = self._need_three_d_secure(record, source_data)
        try:
            if three_d_secure:
                res = stripe.Source.create(
                    **self._prepare_source(record, source=source, **kwargs))
                if res['status'] == 'chargeable':
                    # 3D secure have been not activated or not ready
                    # for this customer
                    three_d_secure = False
                else:
                    return res
            if not three_d_secure:
                return stripe.Charge.create(
                    **self._prepare_charge(record, source=source, **kwargs))
        except stripe.error.CardError as e:
            raise UserError(self._get_error_message(e.code))

    def _prepare_odoo_transaction(self, cart, transaction, **kwargs):
        res = super(PaymentService, self).\
            _prepare_odoo_transaction(cart, transaction, **kwargs)
        res.update({
            'amount': transaction['amount']/100.,
            'external_id': transaction['id'],
            'state': MAP_SOURCE_STATE[transaction['status']],
            'data': json.dumps(transaction),
        })
        if transaction.get('redirect', {}).get('url'):
            res['url'] = transaction['redirect']['url']
        risk_level = transaction.get('outcome', {}).get('risk_level')
        if risk_level:
            res['risk_level'] = risk_level
        return res

    def get_transaction_state(self, transaction):
        source = stripe.Source.retrieve(
            transaction.external_id, api_key=self._api_key)
        return MAP_SOURCE_STATE[source['status']]

    def _prepare_odoo_transaction_from_charge(self, charge):
        return {
            'amount': charge['amount']/100.,
            'external_id': charge['id'],
            'state': MAP_SOURCE_STATE[charge['status']],
            'risk_level': charge.get('outcome', {}).get('risk_level'),
            'data': json.dumps(charge),
        }

    def capture(self, transaction, amount):
        if transaction.external_id.startswith('src_'):
            # Transaction is a source convert it to a charge
            charge = stripe.Charge.create(
                currency=transaction.currency_id.name,
                source=transaction.external_id,
                description=transaction.name,
                capture=True,
                amount=int(amount * 100),
                api_key=self._api_key)
            vals = self._prepare_odoo_transaction_from_charge(charge)
            transaction.write(vals)