changeset 0852decc2eb9 in modules/account_payment_stripe:default
details: 
https://hg.tryton.org/modules/account_payment_stripe?cmd=changeset;node=0852decc2eb9
description:
        Add wizard to delete customer source

        issue9501
        review323711002
diffstat:

 CHANGELOG                                 |   1 +
 __init__.py                               |   2 +
 payment.py                                |  72 ++++++++++++++++++++++++++++++-
 payment.xml                               |  17 +++++++
 tests/scenario_account_payment_stripe.rst |  13 +++++
 view/customer_form.xml                    |   5 +-
 view/customer_source_detach_ask_form.xml  |   9 +++
 7 files changed, 117 insertions(+), 2 deletions(-)

diffs (210 lines):

diff -r b17ec8f1c68c -r 0852decc2eb9 CHANGELOG
--- a/CHANGELOG Mon Jul 13 20:54:31 2020 +0200
+++ b/CHANGELOG Tue Aug 11 21:47:51 2020 +0200
@@ -1,3 +1,4 @@
+* Add wizard to delete customer source
 * Add webhook for payment intent canceled
 
 Version 5.6.0 - 2020-05-04
diff -r b17ec8f1c68c -r 0852decc2eb9 __init__.py
--- a/__init__.py       Mon Jul 13 20:54:31 2020 +0200
+++ b/__init__.py       Tue Aug 11 21:47:51 2020 +0200
@@ -19,11 +19,13 @@
         payment.Journal,
         payment.Group,
         payment.Payment,
+        payment.CustomerSourceDetachAsk,
         party.Party,
         ir.Cron,
         module='account_payment_stripe', type_='model')
     Pool.register(
         payment.Checkout,
+        payment.CustomerSourceDetach,
         party.Replace,
         module='account_payment_stripe', type_='wizard')
     Pool.register(
diff -r b17ec8f1c68c -r 0852decc2eb9 payment.py
--- a/payment.py        Mon Jul 13 20:54:31 2020 +0200
+++ b/payment.py        Tue Aug 11 21:47:51 2020 +0200
@@ -22,7 +22,8 @@
 from trytond.sendmail import sendmail_transactional
 from trytond.transaction import Transaction
 from trytond.url import http_host
-from trytond.wizard import Wizard, StateAction
+from trytond.wizard import (
+    Wizard, StateAction, StateView, StateTransition, Button)
 
 from trytond.modules.account_payment.exceptions import (
     ProcessError, PaymentValidationError)
@@ -1449,6 +1450,10 @@
                     'invisible': ~Eval('stripe_checkout_needed', False),
                     'depends': ['stripe_checkout_needed'],
                     },
+                'detach_source': {
+                    'invisible': ~Eval('stripe_customer_id'),
+                    'depends': ['stripe_customer_id'],
+                    },
                 })
 
     def get_stripe_checkout_needed(self, name):
@@ -1628,6 +1633,30 @@
                 name = '****' + source.sepa_debit.last4
         return name
 
+    @classmethod
+    @ModelView.button_action(
+        'account_payment_stripe.wizard_customer_source_detach')
+    def detach_source(cls, customers):
+        pass
+
+    def delete_source(self, source):
+        try:
+            if source in dict(self.payment_methods()):
+                stripe.PaymentMethod.detach(
+                    source,
+                    api_key=self.stripe_account.secret_key)
+            else:
+                stripe.Customer.delete_source(
+                    self.stripe_customer_id,
+                    source,
+                    api_key=self.stripe_account.secret_key)
+        except (stripe.error.RateLimitError,
+                stripe.error.APIConnectionError) as e:
+            logger.warning(str(e))
+            raise
+        self._sources_cache.clear()
+        self._payment_methods_cache.clear()
+
     def payment_methods(self):
         methods = self._payment_methods_cache.get(self.id)
         if methods is not None:
@@ -1742,3 +1771,44 @@
 class CheckoutPage(Report):
     "Stripe Checkout"
     __name__ = 'account.payment.stripe.checkout'
+
+
+class CustomerSourceDetach(Wizard):
+    "Detach Customer Source"
+    __name__ = 'account.payment.stripe.customer.source.detach'
+    start_state = 'ask'
+    ask = StateView(
+        'account.payment.stripe.customer.source.detach.ask',
+        'account_payment_stripe.customer_source_detach_ask_view_form', [
+            Button("Cancel", 'end', 'tryton-cancel'),
+            Button("Detach", 'detach', 'tryton-ok', default=True),
+            ])
+    detach = StateTransition()
+
+    def default_ask(self, fields):
+        default = {}
+        if 'customer' in fields:
+            default['customer'] = self.record.id
+        return default
+
+    def transition_detach(self):
+        self.record.delete_source(self.ask.source)
+        return 'end'
+
+
+class CustomerSourceDetachAsk(ModelView):
+    "Detach Customer Source"
+    __name__ = 'account.payment.stripe.customer.source.detach.ask'
+
+    customer = fields.Many2One(
+        'account.payment.stripe.customer', "Customer", readonly=True)
+    source = fields.Selection('get_sources', "Source", required=True)
+
+    @fields.depends('customer')
+    def get_sources(self):
+        sources = [('', '')]
+        if self.customer:
+            sources.extend(
+                dict(set(self.customer.sources())
+                    | set(self.customer.payment_methods())).items())
+        return sources
diff -r b17ec8f1c68c -r 0852decc2eb9 payment.xml
--- a/payment.xml       Mon Jul 13 20:54:31 2020 +0200
+++ b/payment.xml       Tue Aug 11 21:47:51 2020 +0200
@@ -302,6 +302,11 @@
             <field name="model"
                 search="[('model', '=', 'account.payment.stripe.customer')]"/>
         </record>
+        <record model="ir.model.button" id="customer_source_detach_button">
+            <field name="name">detach_source</field>
+            <field name="string">Detach Source</field>
+            <field name="model" search="[('model', '=', 
'account.payment.stripe.customer')]"/>
+        </record>
 
         <record model="ir.action.wizard" id="wizard_checkout">
             <field name="name">Stripe Checkout</field>
@@ -325,6 +330,18 @@
             <field name="template_extension">html</field>
         </record>
 
+        <record model="ir.action.wizard" id="wizard_customer_source_detach">
+            <field name="name">Detach Source</field>
+            <field 
name="wiz_name">account.payment.stripe.customer.source.detach</field>
+            <field name="model">account.payment.stripe.customer</field>
+        </record>
+
+        <record model="ir.ui.view" id="customer_source_detach_ask_view_form">
+            <field 
name="model">account.payment.stripe.customer.source.detach.ask</field>
+            <field name="type">form</field>
+            <field name="name">customer_source_detach_ask_form</field>
+        </record>
+
         <record model="ir.cron" id="cron_charge">
             <field name="method">account.payment|stripe_charge</field>
             <field name="interval_number" eval="15"/>
diff -r b17ec8f1c68c -r 0852decc2eb9 tests/scenario_account_payment_stripe.rst
--- a/tests/scenario_account_payment_stripe.rst Mon Jul 13 20:54:31 2020 +0200
+++ b/tests/scenario_account_payment_stripe.rst Tue Aug 11 21:47:51 2020 +0200
@@ -219,6 +219,19 @@
     >>> payment.state
     'succeeded'
 
+Detach source::
+
+    >>> detach = Wizard(
+    ...     'account.payment.stripe.customer.source.detach', [stripe_customer])
+    >>> detach.form.source = source_id
+    >>> detach.execute('detach')
+
+    >>> cus = stripe.Customer.retrieve(stripe_customer.stripe_customer_id)
+    >>> len(cus.sources)
+    0
+    >>> len(stripe.PaymentMethod.list(customer=cus.id, type='card'))
+    0
+
 Delete customer::
 
     >>> stripe_customer.delete()
diff -r b17ec8f1c68c -r 0852decc2eb9 view/customer_form.xml
--- a/view/customer_form.xml    Mon Jul 13 20:54:31 2020 +0200
+++ b/view/customer_form.xml    Tue Aug 11 21:47:51 2020 +0200
@@ -11,7 +11,10 @@
     <newline/>
     <label name="stripe_customer_id"/>
     <field name="stripe_customer_id"/>
-    <button name="stripe_checkout" colspan="2"/>
+    <group id="buttons" col="-1" colspan="4">
+        <button name="stripe_checkout"/>
+        <button name="detach_source"/>
+    </group>
     <label name="stripe_error_message"/>
     <field name="stripe_error_message"/>
     <newline/>
diff -r b17ec8f1c68c -r 0852decc2eb9 view/customer_source_detach_ask_form.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/view/customer_source_detach_ask_form.xml  Tue Aug 11 21:47:51 2020 +0200
@@ -0,0 +1,9 @@
+<?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. -->
+<form col="2">
+    <label name="customer"/>
+    <field name="customer"/>
+    <label name="source"/>
+    <field name="source"/>
+</form>

Reply via email to