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

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

Remove item_id from PaymentItem?.

  • Property svn:keywords set to Id
File size: 8.1 KB
Line 
1## $Id: payment.py 12800 2015-03-20 13:07:29Z 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.payments.interfaces import (
31    IPayment, STATE_UNPAID, STATE_FAILED, STATE_PAID,
32    IPaymentGatewayService, IPayer, IPaymentItem, IPayee,
33    IPaymentGatewayServicesLister, IPayableFinder, IPayerFinder,
34    IPaymentWaitingForGatewayEvent, IPaymentFinishedEvent,
35    )
36
37
38def format_amount(amount, currency):
39    """Turn `amount`, `currency` into a readable string.
40    """
41    cncy_map = {'USD': u'US$', 'EUR': u'\u20ac', 'NGN': u'\u20a6'}
42    currency = cncy_map.get(currency, currency)
43    return '%s %s' % (currency, '{:,.2f}'.format(amount))
44
45
46def get_payment(payment_id):
47    """Get payment by payment id.
48
49    If no such payment can be found in catalog, return none.
50    """
51    cat = getUtility(ICatalog, name='payments_catalog')
52    result_set = [x for x in cat.searchResults(
53        payment_id=(payment_id, payment_id))]
54    if len(result_set):
55        return result_set[0]
56    return None
57
58
59def get_payments_from_payer_id(payer_id):
60    """Get payments by payer id.
61
62    If no such payment can be found in catalog, return none.
63    """
64    cat = getUtility(ICatalog, name='payments_catalog')
65    result_set = [x for x in cat.searchResults(
66        payer_id=(payer_id, payer_id))]
67    if len(result_set):
68        return result_set
69    return None
70
71
72def get_payments_from_payable_id(payable_id):
73    """Get payments by payer id.
74
75    If no such payment can be found in catalog, return none.
76    """
77    cat = getUtility(ICatalog, name='payments_catalog')
78    result_set = [x for x in cat.searchResults(
79        payable_id=(payable_id, payable_id))]
80    if len(result_set):
81        return result_set
82    return None
83
84
85def find_payable_from_payable_id(payable_id):
86    """Find a payable from its id.
87
88    Looks up all registered IPayableFinders and returns the first
89    positive result found.
90    """
91    for name, util in getUtilitiesFor(IPayableFinder):
92        result = util.get_payable_by_id(payable_id)
93        if result is not None:
94            return result
95    return None
96
97
98def find_payer_from_payer_id(payer_id):
99    """Find a payer from its id.
100
101    Looks up all registered IPayerFinders and returns the first
102    positive result found.
103    """
104    for name, util in getUtilitiesFor(IPayerFinder):
105        result = util.get_payer_by_id(payer_id)
106        if result is not None:
107            return result
108    return None
109
110
111def format_payment_item_values(payment_item_values, currency):
112    """Format tuples (description, currency, amount) for output.
113
114    `currency` passed in is the 'target' currency.
115
116    Returns a list of formatted values. Last item is total sum.
117    XXX: we do not really respect currency. If different items
118         have different currencies, we are choked.
119    """
120    result = []
121    total = decimal.Decimal("0.00")
122    for descr, item_currency, amount in payment_item_values:
123        total += amount
124        if item_currency != currency:
125            raise ValueError(
126                "Different currencies in payment items not supported.")
127        result.append((descr, '%s %0.2f' % (item_currency, amount)))
128    result.append((_('Total'), '%s %0.2f' % (currency, total)))
129    return result
130
131
132def get_payment_providers():
133    """Get all payment providers registered.
134    """
135    return dict(
136        getUtilitiesFor(IPaymentGatewayService)
137    )
138
139
140class PaymentWaitingForGatewayEvent(object):
141    grok.implements(IPaymentWaitingForGatewayEvent)
142
143    def __init__(self, obj):
144        self.object = obj
145
146
147class PaymentFinishedEvent(object):
148    grok.implements(IPaymentFinishedEvent)
149
150    def __init__(self, obj):
151        self.object = obj
152
153
154class PaymentGatewayServicesLister(grok.GlobalUtility):
155    grok.implements(IPaymentGatewayServicesLister)
156
157    def __call__(self):
158        """Get all services of payment gateways registered.
159        """
160        return get_payment_providers()
161
162
163class PaymentProviderServiceBase(grok.GlobalUtility):
164    """Base for IPaymentGatewayServices.
165    """
166    grok.baseclass()
167    grok.implements(IPaymentGatewayService)
168
169    title = u'Sample Credit Card Service'
170
171    def store(self, payment):
172        """Store `payment` in site.
173        """
174        site = grok.getSite()
175        payments = site['payments']
176        if payment.payment_id in payments:
177            del site['payments'][payment.payment_id]
178        site['payments'][payment.payment_id] = payment
179
180
181class Payment(grok.Model):
182    """This is a payment.
183    """
184    grok.implements(IPayment)
185    grok.provides(IPayment)
186
187    form_fields_interface = IPayment
188
189    @property
190    def amount(self):
191        """Total amount.
192
193        Sum of all contained payment items.
194        """
195        amounts = [item.amount for item in self.payment_items]
196        return sum(amounts, decimal.Decimal("0.00"))
197
198    def __init__(self, payer, payable, payee=None):
199        super(Payment, self).__init__()
200        items = tuple([x for x in payable.payment_items])
201        self.payment_items = items
202        self.payer_id = payer.payer_id
203        self.payable_id = payable.payable_id
204        self.title = payable.title
205        self.creation_date = datetime.utcnow()
206        self.payment_date = None
207        self.payment_id = u'PAY_' + unicode(uuid.uuid4().hex)
208        self.state = STATE_UNPAID
209        self.currency = payable.currency
210        if payee is not None:
211            self.payee_id = payee.payee_id
212        return
213
214    def approve(self, payment_date=None):
215        """A payment was approved.
216
217        Successful ending; the payment is marked as payed.
218
219        If `payment_date` is given, it must be a datetime object
220        giving a datetime in UTC timezone.
221
222        Raises ObjectModifiedEvent.
223        """
224        if payment_date is None:
225            payment_date = datetime.utcnow()
226        self.payment_date = payment_date
227        self.state = STATE_PAID
228        notify(grok.ObjectModifiedEvent(self))
229
230    def mark_failed(self, reason=None):
231        """Mark payment as failed.
232
233        Raises ObjectModifiedEvent.
234        """
235        self.state = STATE_FAILED
236        notify(grok.ObjectModifiedEvent(self))
237
238Payment = attrs_to_fields(Payment, omit="amount")
239
240
241@attrs_to_fields
242class Payer(object):
243    """A Payer for testing.
244
245    It cannot be stored in ZODB.
246    """
247    grok.implements(IPayer)
248
249
250@attrs_to_fields
251class PaymentItem(object):
252
253    grok.implements(IPaymentItem)
254
255    def __init__(
256            self, title=u"", amount=decimal.Decimal("0.00")):
257        super(PaymentItem, self).__init__()
258        self.title = title
259        self.amount = amount
260
261    def __repr__(self):
262        result = "%s(title=%r, amount=%r)" % (
263            self.__class__.__name__, self.title, self.amount)
264        return result
265
266    def to_string(self):
267        """A string representation that can be used in exports.
268
269        Returned is a unicode string of format
270        ``(u'<ITEM_ID>', u'<TITLE>', u'<AMOUNT>')``.
271        """
272        string = u"(u'%s', u'%s')" % (self.title, self.amount)
273        string = string.replace("u'None'", "None")
274        return string
275
276
277@attrs_to_fields
278class Payee(object):
279    """Someone being paid.
280
281    This is for testing only and cannot be stored in ZODB.
282    """
283    grok.implements(IPayee)
Note: See TracBrowser for help on using the repository browser.