Add wizard to create direct debit for configured parties

issue10772
review379071002
This commit is contained in:
Cédric Krier committed 2021-10-11 18:35:28 +02:00
1 parent c3b1397c5a
commit 6c4bef912f
7 files changed
+233 -134

No files matched your search

+1
View File
@@ -21,6 +21,7 @@ def register():
payment.Payment,
payment.CustomerSourceDetachAsk,
party.Party,
party.PartyReceptionDirectDebit,
ir.Cron,
module='account_payment_stripe', type_='model')
Pool.register(
+144
View File
@@ -0,0 +1,144 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.model import fields
from trytond.pyson import Eval
class StripeCustomerMethodMixin:
__slots__ = ()
stripe_account = fields.Function(fields.Many2One(
'account.payment.stripe.account', "Stripe Account"),
'on_change_with_stripe_account')
stripe_customer = fields.Many2One(
'account.payment.stripe.customer', "Stripe Customer",
domain=[
('party', '=', Eval('party', -1)),
('stripe_account', '=', Eval('stripe_account', -1)),
],
states={
'invisible': Eval('process_method') != 'stripe',
'readonly': (
~Eval('party') | (Eval('party', -1) < 0)
| ~Eval('stripe_account') | (Eval('stripe_account', -1) < 0)),
},
depends=['party', 'stripe_account', 'process_method'])
stripe_customer_source = fields.Char(
"Stripe Customer Source",
states={
'invisible': (
(Eval('process_method') != 'stripe')
| ~Eval('stripe_customer')
| Eval('stripe_customer_payment_method')),
},
depends=[
'process_method', 'stripe_customer',
'stripe_customer_payment_method'])
# Use Function field with selection to avoid to query Stripe
# to validate the value
stripe_customer_source_selection = fields.Function(fields.Selection(
'get_stripe_customer_sources', "Stripe Customer Source",
states={
'invisible': (
(Eval('process_method') != 'stripe')
| ~Eval('stripe_customer')
| Eval('stripe_customer_payment_method')),
},
depends=[
'process_method', 'stripe_customer',
'stripe_customer_payment_method']),
'get_stripe_customer_source', setter='set_stripe_customer_source')
stripe_customer_payment_method = fields.Char(
"Stripe Payment Method",
states={
'invisible': (
(Eval('process_method') != 'stripe')
| ~Eval('stripe_customer')
| Eval('stripe_customer_source')),
},
depends=[
'process_method', 'stripe_customer', 'stripe_customer_source'])
# Use Function field with selection to avoid to query Stripe
# to validate the value
stripe_customer_payment_method_selection = fields.Function(
fields.Selection(
'get_stripe_customer_payment_methods',
"Stripe Customer Payment Method",
states={
'invisible': (
(Eval('process_method') != 'stripe')
| ~Eval('stripe_customer')
| Eval('stripe_customer_source')),
},
depends=[
'process_method', 'stripe_customer',
'stripe_customer_source']),
'get_stripe_customer_payment_method',
setter='set_stripe_customer_payment_method')
def on_change_party(self):
try:
super().on_change_party()
except AttributeError:
pass
self.stripe_customer = None
self.stripe_customer_source = None
self.stripe_customer_source_selection = None
@fields.depends('journal')
def on_change_with_stripe_account(self, name=None):
if self.journal and self.journal.process_method == 'stripe':
return self.journal.stripe_account.id
@fields.depends('stripe_customer', 'stripe_customer_source')
def get_stripe_customer_sources(self):
sources = [('', '')]
if self.stripe_customer:
sources.extend(self.stripe_customer.sources())
if (self.stripe_customer_source
and self.stripe_customer_source not in dict(sources)):
sources.append(
(self.stripe_customer_source, self.stripe_customer_source))
return sources
@fields.depends(
'stripe_customer_source_selection',
'stripe_customer_source')
def on_change_stripe_customer_source_selection(self):
self.stripe_customer_source = self.stripe_customer_source_selection
def get_stripe_customer_source(self, name):
return self.stripe_customer_source
@classmethod
def set_stripe_customer_source(cls, payments, name, value):
pass
@fields.depends('stripe_customer', 'stripe_customer_payment_method')
def get_stripe_customer_payment_methods(self):
methods = [('', '')]
if self.stripe_customer:
methods.extend(self.stripe_customer.payment_methods())
if (self.stripe_customer_payment_method
and self.stripe_customer_payment_method not in dict(methods)):
methods.append(
(self.stripe_customer_payment_method,
self.stripe_customer_payment_method))
return methods
@fields.depends(
'stripe_customer_payment_method_selection',
'stripe_customer_payment_method')
def on_change_stripe_customer_payment_method_selection(self):
self.stripe_customer_payment_method = (
self.stripe_customer_payment_method_selection)
def get_stripe_customer_payment_method(self, name):
return self.stripe_customer_payment_method
@classmethod
def set_stripe_customer_payment_method(cls, payments, name, value):
pass
+15
View File
@@ -3,6 +3,8 @@
from trytond.pool import PoolMeta, Pool
from trytond.model import fields
from .common import StripeCustomerMethodMixin
class Party(metaclass=PoolMeta):
__name__ = 'party.party'
@@ -29,6 +31,19 @@ class Party(metaclass=PoolMeta):
Customer.__queue__.stripe_update(to_update)
class PartyReceptionDirectDebit(
StripeCustomerMethodMixin, metaclass=PoolMeta):
__name__ = 'party.party.reception_direct_debit'
def _get_payment(self, line, date, amount):
payment = super()._get_payment(line, date, amount)
self.stripe_customer = self.stripe_customer
self.stripe_customer_source = self.stripe_customer_source
self.stripe_customer_payment_method = (
self.stripe_customer_payment_method)
return payment
class Replace(metaclass=PoolMeta):
__name__ = 'party.replace'
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="party_reception_direct_debit_view_form">
<field name="model">party.party.reception_direct_debit</field>
<field name="inherit" ref="account_payment.party_reception_direct_debit_view_form"/>
<field name="name">party_reception_direct_debit_form</field>
</record>
</data>
</tryton>
+42 -134
View File
@@ -16,7 +16,7 @@ from trytond.i18n import gettext
from trytond.model import (
ModelSQL, ModelView, Workflow, DeactivableMixin, fields, dualmethod)
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval, Bool
from trytond.pyson import Eval
from trytond.report import Report, get_email
from trytond.rpc import RPC
from trytond.sendmail import sendmail_transactional
@@ -30,6 +30,8 @@ from trytond.modules.account_payment.exceptions import (
ProcessError, PaymentValidationError)
from trytond.modules.currency.fields import Monetary
from .common import StripeCustomerMethodMixin
logger = logging.getLogger(__name__)
stripe.max_network_retries = config.getint(
'account_payment_stripe', 'max_network_retries', default=3)
@@ -118,7 +120,7 @@ class CheckoutMixin:
}
class Payment(CheckoutMixin, metaclass=PoolMeta):
class Payment(StripeCustomerMethodMixin, CheckoutMixin, metaclass=PoolMeta):
__name__ = 'account.payment'
stripe_checkout_needed = fields.Function(
@@ -192,76 +194,6 @@ class Payment(CheckoutMixin, metaclass=PoolMeta):
states={
'invisible': ~Eval('stripe_dispute_status'),
})
stripe_customer = fields.Many2One(
'account.payment.stripe.customer', "Stripe Customer",
domain=[
('party', '=', Eval('party', -1)),
('stripe_account', '=', Eval('stripe_account', -1)),
],
states={
'invisible': Eval('process_method') != 'stripe',
'required': Bool(Eval('stripe_customer_source')),
'readonly': (~Eval('state').in_(['draft', 'approved'])
| Eval('stripe_token') | Eval('stripe_payment_intent_id')),
},
depends=['party', 'stripe_account', 'process_method',
'stripe_customer_source', 'stripe_token',
'stripe_payment_intent_id', 'state'])
stripe_customer_source = fields.Char(
"Stripe Customer Source",
states={
'invisible': ((Eval('process_method') != 'stripe')
| Eval('stripe_token')
| Eval('stripe_payment_intent_id')
| ~Eval('stripe_customer')),
'readonly': ~Eval('state').in_(['draft', 'approved']),
},
depends=['process_method', 'stripe_token', 'stripe_payment_intent_id',
'stripe_customer', 'state'])
# Use Function field with selection to avoid to query Stripe
# to validate the value
stripe_customer_source_selection = fields.Function(fields.Selection(
'get_stripe_customer_sources', "Stripe Customer Source",
states={
'invisible': ((Eval('process_method') != 'stripe')
| Eval('stripe_token')
| Eval('stripe_payment_intent_id')
| ~Eval('stripe_customer')),
'readonly': ~Eval('state').in_(['draft', 'approved']),
},
depends=[
'process_method', 'stripe_token', 'stripe_customer', 'state']),
'get_stripe_customer_source', setter='set_stripe_customer_source')
stripe_customer_payment_method = fields.Char(
"Stripe Payment Method",
states={
'invisible': ((Eval('process_method') != 'stripe')
| Eval('stripe_token')
| ~Eval('stripe_customer')),
'readonly': (~Eval('state').in_(['draft', 'approved'])
| Eval('stripe_payment_intent_id')),
},
depends=['process_method', 'stripe_token', 'stripe_customer', 'state'])
# Use Function field with selection to avoid to query Stripe
# to validate the value
stripe_customer_payment_method_selection = fields.Function(
fields.Selection(
'get_stripe_customer_payment_methods',
"Stripe Customer Payment Method",
states={
'invisible': ((Eval('process_method') != 'stripe')
| Eval('stripe_token')
| ~Eval('stripe_customer')),
'readonly': (~Eval('state').in_(['draft', 'approved'])
| Eval('stripe_payment_intent_id')),
},
depends=[
'process_method', 'stripe_token', 'stripe_customer', 'state']),
'get_stripe_customer_payment_method',
setter='set_stripe_customer_payment_method')
stripe_account = fields.Function(fields.Many2One(
'account.payment.stripe.account', "Stripe Account"),
'on_change_with_stripe_account')
stripe_amount = fields.Function(
fields.Integer("Stripe Amount"),
'get_stripe_amount', setter='set_stripe_amount')
@@ -278,6 +210,44 @@ class Payment(CheckoutMixin, metaclass=PoolMeta):
@classmethod
def __setup__(cls):
super(Payment, cls).__setup__()
cls.stripe_customer.states['readonly'] = (
~Eval('state').in_(['draft', 'approved'])
| Eval('stripe_token')
| Eval('stripe_payment_intent_id'))
cls.stripe_customer.depends.extend(
['state', 'stripe_token', 'stripe_payment_intent_id'])
cls.stripe_customer_source.states['invisible'] |= (
Eval('stripe_token') | Eval('stripe_payment_intent_id'))
cls.stripe_customer_source.states['readonly'] = (
~Eval('state').in_(['draft', 'approved']))
cls.stripe_customer_source.depends.extend(
['stripe_token', 'stripe_payment_intent_id', 'state'])
cls.stripe_customer_source_selection.states['invisible'] |= (
Eval('stripe_token') | Eval('stripe_payment_intent_id'))
cls.stripe_customer_source_selection.states['readonly'] = (
~Eval('state').in_(['draft', 'approved']))
cls.stripe_customer_source_selection.depends.extend(
['stripe_token', 'stripe_payment_intent_id', 'state'])
cls.stripe_customer_payment_method.states['invisible'] |= (
Eval('stripe_token'))
cls.stripe_customer_payment_method.states['readonly'] = (
~Eval('state').in_(['draft', 'approved'])
| Eval('stripe_payment_intent_id'))
cls.stripe_customer_payment_method.depends.extend(
['stripe_token', 'state', 'stripe_payment_intent_id'])
cls.stripe_customer_payment_method_selection.states['invisible'] |= (
Eval('stripe_token'))
cls.stripe_customer_payment_method_selection.states['readonly'] = (
~Eval('state').in_(['draft', 'approved'])
| Eval('stripe_payment_intent_id'))
cls.stripe_customer_payment_method_selection.depends.extend(
['stripe_token', 'state', 'stripe_payment_intent_id'])
cls.amount.states['readonly'] &= ~Eval('stripe_capture_needed')
cls.amount.depends.append('stripe_capture_needed')
cls.stripe_amount.states.update(cls.amount.states)
@@ -330,63 +300,6 @@ class Payment(CheckoutMixin, metaclass=PoolMeta):
def default_stripe_idempotency_key(cls):
return uuid.uuid4().hex
@fields.depends('party')
def on_change_party(self):
super(Payment, self).on_change_party()
self.stripe_customer = None
self.stripe_customer_source = None
self.stripe_customer_source_selection = None
@fields.depends('stripe_customer', 'stripe_customer_source')
def get_stripe_customer_sources(self):
sources = [('', '')]
if self.stripe_customer:
sources.extend(self.stripe_customer.sources())
if (self.stripe_customer_source
and self.stripe_customer_source not in dict(sources)):
sources.append(
(self.stripe_customer_source, self.stripe_customer_source))
return sources
@fields.depends(
'stripe_customer_source_selection',
'stripe_customer_source')
def on_change_stripe_customer_source_selection(self):
self.stripe_customer_source = self.stripe_customer_source_selection
def get_stripe_customer_source(self, name):
return self.stripe_customer_source
@classmethod
def set_stripe_customer_source(cls, payments, name, value):
pass
@fields.depends('stripe_customer', 'stripe_customer_payment_method')
def get_stripe_customer_payment_methods(self):
methods = [('', '')]
if self.stripe_customer:
methods.extend(self.stripe_customer.payment_methods())
if (self.stripe_customer_payment_method
and self.stripe_customer_payment_method not in dict(methods)):
methods.append(
(self.stripe_customer_payment_method,
self.stripe_customer_payment_method))
return methods
@fields.depends(
'stripe_customer_payment_method_selection',
'stripe_customer_payment_method')
def on_change_stripe_customer_payment_method_selection(self):
self.stripe_customer_payment_method = (
self.stripe_customer_payment_method_selection)
def get_stripe_customer_payment_method(self, name):
return self.stripe_customer_payment_method
@classmethod
def set_stripe_customer_payment_method(cls, payments, name, value):
pass
@fields.depends('process_method',
'stripe_token', 'stripe_payment_intent_id',
'stripe_customer_source', 'stripe_customer_source_selection',
@@ -406,11 +319,6 @@ class Payment(CheckoutMixin, metaclass=PoolMeta):
and not self.stripe_capture
and not self.stripe_captured)
@fields.depends('journal')
def on_change_with_stripe_account(self, name=None):
if self.journal and self.journal.process_method == 'stripe':
return self.journal.stripe_account.id
def get_stripe_amount(self, name):
return int(self.amount * 10 ** self.currency.digits)
+1
View File
@@ -7,4 +7,5 @@ depends:
party
xml:
payment.xml
party.xml
message.xml
@@ -0,0 +1,18 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='currency']" position="after">
<label name="stripe_customer" string="Customer:"/>
<field name="stripe_customer" colspan="3"/>
<label name="stripe_customer_source_selection" string="Source:"/>
<field name="stripe_customer_source_selection" colspan="3"/>
<label name="stripe_customer_payment_method_selection" string="Payment Method:"/>
<field name="stripe_customer_payment_method_selection" colspan="3"/>
<field name="stripe_customer_source" colspan="4" invisible="1"/>
<field name="stripe_customer_payment_method" colspan="4" invisible="1"/>
</xpath>
</data>