source: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/payment.py @ 12741

Last change on this file since 12741 was 12741, checked in by uli, 10 years ago

Merge changes from uli-payments back into trunk.

  • Property svn:keywords set to Id
File size: 6.9 KB
Line 
1## $Id: payment.py 12741 2015-03-12 05:29:43Z uli $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""
19These are the payment tickets.
20"""
21import decimal
22import grok
23import uuid
24from datetime import datetime
25from zope.catalog.interfaces import ICatalog
26from zope.component import getUtilitiesFor, getUtility
27from zope.event import notify
28from waeup.ikoba.interfaces import MessageFactory as _
29from waeup.ikoba.utils.helpers import attrs_to_fields
30from waeup.ikoba.utils.logger import Logger
31from waeup.ikoba.payments.interfaces import (
32    IPayment, STATE_UNPAID, STATE_FAILED, STATE_PAID,
33    IPaymentGatewayService, IPayer, IPaymentItem, IPayee,
34    IPaymentGatewayServicesLister, IPayableFinder, IPayerFinder,
35    IPaymentWaitingForGatewayEvent, IPaymentFinishedEvent,
36    )
37
38
39def format_amount(amount, currency):
40    """Turn `amount`, `currency` into a readable string.
41    """
42    cncy_map = {'USD': u'US$', 'EUR': u'\u20ac', 'NGN': u'\u20a6'}
43    currency = cncy_map.get(currency, currency)
44    return '%s %s' % (currency, '{:,.2f}'.format(amount))
45
46
47def get_payment(payment_id):
48    """Get payment by payment id.
49
50    If no such payment can be found in catalog, return none.
51    """
52    cat = getUtility(ICatalog, name='payments_catalog')
53    result_set = [x for x in cat.searchResults(
54        payment_id=(payment_id, payment_id))]
55    if len(result_set):
56        return result_set[0]
57    return None
58
59
60def find_payable_from_payable_id(payable_id):
61    """Find a payable from its id.
62
63    Looks up all registered IPayableFinders and returns the first
64    positive result found.
65    """
66    for name, util in getUtilitiesFor(IPayableFinder):
67        result = util.get_payable_by_id(payable_id)
68        if result is not None:
69            return result
70    return None
71
72
73def find_payer_from_payer_id(payer_id):
74    """Find a payer from its id.
75
76    Looks up all registered IPayerFinders and returns the first
77    positive result found.
78    """
79    for name, util in getUtilitiesFor(IPayerFinder):
80        result = util.get_payer_by_id(payer_id)
81        if result is not None:
82            return result
83    return None
84
85
86def format_payment_item_values(payment_item_values, currency):
87    """Format tuples (description, currency, amount) for output.
88
89    `currency` passed in is the 'target' currency.
90
91    Returns a list of formated values. Last item is total sum.
92    XXX: we do not really respect currency. If different items
93         have different currencies, we are choked.
94    """
95    result = []
96    total = decimal.Decimal("0.00")
97    for descr, item_currency, amount in payment_item_values:
98        total += amount
99        if item_currency != currency:
100            raise ValueError(
101                "Different currencies in payment items not supported.")
102        result.append((descr, '%s %0.2f' % (item_currency, amount)))
103    result.append((_('Total'), '%s %0.2f' % (currency, total)))
104    return result
105
106
107def get_payment_providers():
108    """Get all payment providers registered.
109    """
110    return dict(
111        getUtilitiesFor(IPaymentGatewayService)
112    )
113
114
115class PaymentWaitingForGatewayEvent(object):
116    grok.implements(IPaymentWaitingForGatewayEvent)
117
118    def __init__(self, obj):
119        self.object = obj
120
121
122class PaymentFinishedEvent(object):
123    grok.implements(IPaymentFinishedEvent)
124
125    def __init__(self, obj):
126        self.object = obj
127
128
129class PaymentGatewayServicesLister(grok.GlobalUtility):
130    grok.implements(IPaymentGatewayServicesLister)
131
132    def __call__(self):
133        """Get all services of payment gateways registered.
134        """
135        return get_payment_providers()
136
137
138class PaymentProviderServiceBase(grok.GlobalUtility):
139    """Base for IPaymentGatewayServices.
140    """
141    grok.baseclass()
142    grok.implements(IPaymentGatewayService)
143
144    title = u'Sample Credit Card Service'
145
146    def store(self, payment):
147        """Store `payment` in site.
148        """
149        site = grok.getSite()
150        payments = site['payments']
151        if payment.payment_id in payments:
152            del site['payments'][payment.payment_id]
153        site['payments'][payment.payment_id] = payment
154
155
156@attrs_to_fields
157class Payment(grok.Model, Logger):
158    """This is a payment.
159    """
160    grok.implements(IPayment)
161    grok.provides(IPayment)
162
163    logger_name = 'waeup.ikoba.${sitename}.payments'
164    logger_filename = 'payments.log'
165    logger_format_str = '"%(asctime)s","%(user)s",%(message)s'
166
167    def __init__(self, payer, payable, payee=None):
168        super(Payment, self).__init__()
169        item_amounts = [decimal.Decimal("0.00"), ]
170        item_amounts += [item.amount for item in payable.payment_items]
171        self.amount = sum(item_amounts)
172        self.payer_id = payer.payer_id
173        self.payable_id = payable.payable_id
174        self.title = payable.title
175        self.creation_date = datetime.utcnow()
176        self.payment_date = None
177        self.payment_id = u'PAY_' + unicode(uuid.uuid4().hex)
178        self.state = STATE_UNPAID
179        self.currency = payable.currency
180        if payee is not None:
181            self.payee_id = payee.payee_id
182        return
183
184    def approve(self, payment_date=None):
185        """A payment was approved.
186
187        Successful ending; the payment is marked as payed.
188
189        If `payment_date` is given, it must be a datetime object
190        giving a datetime in UTC timezone.
191
192        Raises ObjectModifiedEvent.
193        """
194        if payment_date is None:
195            payment_date = datetime.utcnow()
196        self.payment_date = payment_date
197        self.state = STATE_PAID
198        notify(grok.ObjectModifiedEvent(self))
199
200    def mark_failed(self, reason=None):
201        """Mark payment as failed.
202
203        Raises ObjectModifiedEvent.
204        """
205        self.state = STATE_FAILED
206        notify(grok.ObjectModifiedEvent(self))
207
208
209@attrs_to_fields
210class Payer(object):
211    """A Payment is for testing.
212
213    It cannot be stored in ZODB.
214    """
215    grok.implements(IPayer)
216
217
218@attrs_to_fields
219class PaymentItem(object):
220
221    grok.implements(IPaymentItem)
222
223    def __init__(
224            self, item_id=u"0", title=u"", amount=decimal.Decimal("0.00")):
225        super(PaymentItem, self).__init__()
226        self.item_id = item_id
227        self.title = title
228        self.amount = amount
229
230
231@attrs_to_fields
232class Payee(object):
233    """Someone being paid.
234
235    This is for testing only and cannot be stored in ZODB.
236    """
237    grok.implements(IPayee)
Note: See TracBrowser for help on using the repository browser.