## $Id: payment.py 12800 2015-03-20 13:07:29Z uli $ ## ## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## """ These are the payment tickets. """ import decimal import grok import uuid from datetime import datetime from zope.catalog.interfaces import ICatalog from zope.component import getUtilitiesFor, getUtility from zope.event import notify from waeup.ikoba.interfaces import MessageFactory as _ from waeup.ikoba.utils.helpers import attrs_to_fields from waeup.ikoba.payments.interfaces import ( IPayment, STATE_UNPAID, STATE_FAILED, STATE_PAID, IPaymentGatewayService, IPayer, IPaymentItem, IPayee, IPaymentGatewayServicesLister, IPayableFinder, IPayerFinder, IPaymentWaitingForGatewayEvent, IPaymentFinishedEvent, ) def format_amount(amount, currency): """Turn `amount`, `currency` into a readable string. """ cncy_map = {'USD': u'US$', 'EUR': u'\u20ac', 'NGN': u'\u20a6'} currency = cncy_map.get(currency, currency) return '%s %s' % (currency, '{:,.2f}'.format(amount)) def get_payment(payment_id): """Get payment by payment id. If no such payment can be found in catalog, return none. """ cat = getUtility(ICatalog, name='payments_catalog') result_set = [x for x in cat.searchResults( payment_id=(payment_id, payment_id))] if len(result_set): return result_set[0] return None def get_payments_from_payer_id(payer_id): """Get payments by payer id. If no such payment can be found in catalog, return none. """ cat = getUtility(ICatalog, name='payments_catalog') result_set = [x for x in cat.searchResults( payer_id=(payer_id, payer_id))] if len(result_set): return result_set return None def get_payments_from_payable_id(payable_id): """Get payments by payer id. If no such payment can be found in catalog, return none. """ cat = getUtility(ICatalog, name='payments_catalog') result_set = [x for x in cat.searchResults( payable_id=(payable_id, payable_id))] if len(result_set): return result_set return None def find_payable_from_payable_id(payable_id): """Find a payable from its id. Looks up all registered IPayableFinders and returns the first positive result found. """ for name, util in getUtilitiesFor(IPayableFinder): result = util.get_payable_by_id(payable_id) if result is not None: return result return None def find_payer_from_payer_id(payer_id): """Find a payer from its id. Looks up all registered IPayerFinders and returns the first positive result found. """ for name, util in getUtilitiesFor(IPayerFinder): result = util.get_payer_by_id(payer_id) if result is not None: return result return None def format_payment_item_values(payment_item_values, currency): """Format tuples (description, currency, amount) for output. `currency` passed in is the 'target' currency. Returns a list of formatted values. Last item is total sum. XXX: we do not really respect currency. If different items have different currencies, we are choked. """ result = [] total = decimal.Decimal("0.00") for descr, item_currency, amount in payment_item_values: total += amount if item_currency != currency: raise ValueError( "Different currencies in payment items not supported.") result.append((descr, '%s %0.2f' % (item_currency, amount))) result.append((_('Total'), '%s %0.2f' % (currency, total))) return result def get_payment_providers(): """Get all payment providers registered. """ return dict( getUtilitiesFor(IPaymentGatewayService) ) class PaymentWaitingForGatewayEvent(object): grok.implements(IPaymentWaitingForGatewayEvent) def __init__(self, obj): self.object = obj class PaymentFinishedEvent(object): grok.implements(IPaymentFinishedEvent) def __init__(self, obj): self.object = obj class PaymentGatewayServicesLister(grok.GlobalUtility): grok.implements(IPaymentGatewayServicesLister) def __call__(self): """Get all services of payment gateways registered. """ return get_payment_providers() class PaymentProviderServiceBase(grok.GlobalUtility): """Base for IPaymentGatewayServices. """ grok.baseclass() grok.implements(IPaymentGatewayService) title = u'Sample Credit Card Service' def store(self, payment): """Store `payment` in site. """ site = grok.getSite() payments = site['payments'] if payment.payment_id in payments: del site['payments'][payment.payment_id] site['payments'][payment.payment_id] = payment class Payment(grok.Model): """This is a payment. """ grok.implements(IPayment) grok.provides(IPayment) form_fields_interface = IPayment @property def amount(self): """Total amount. Sum of all contained payment items. """ amounts = [item.amount for item in self.payment_items] return sum(amounts, decimal.Decimal("0.00")) def __init__(self, payer, payable, payee=None): super(Payment, self).__init__() items = tuple([x for x in payable.payment_items]) self.payment_items = items self.payer_id = payer.payer_id self.payable_id = payable.payable_id self.title = payable.title self.creation_date = datetime.utcnow() self.payment_date = None self.payment_id = u'PAY_' + unicode(uuid.uuid4().hex) self.state = STATE_UNPAID self.currency = payable.currency if payee is not None: self.payee_id = payee.payee_id return def approve(self, payment_date=None): """A payment was approved. Successful ending; the payment is marked as payed. If `payment_date` is given, it must be a datetime object giving a datetime in UTC timezone. Raises ObjectModifiedEvent. """ if payment_date is None: payment_date = datetime.utcnow() self.payment_date = payment_date self.state = STATE_PAID notify(grok.ObjectModifiedEvent(self)) def mark_failed(self, reason=None): """Mark payment as failed. Raises ObjectModifiedEvent. """ self.state = STATE_FAILED notify(grok.ObjectModifiedEvent(self)) Payment = attrs_to_fields(Payment, omit="amount") @attrs_to_fields class Payer(object): """A Payer for testing. It cannot be stored in ZODB. """ grok.implements(IPayer) @attrs_to_fields class PaymentItem(object): grok.implements(IPaymentItem) def __init__( self, title=u"", amount=decimal.Decimal("0.00")): super(PaymentItem, self).__init__() self.title = title self.amount = amount def __repr__(self): result = "%s(title=%r, amount=%r)" % ( self.__class__.__name__, self.title, self.amount) return result def to_string(self): """A string representation that can be used in exports. Returned is a unicode string of format ``(u'', u'', u'<AMOUNT>')``. """ string = u"(u'%s', u'%s')" % (self.title, self.amount) string = string.replace("u'None'", "None") return string @attrs_to_fields class Payee(object): """Someone being paid. This is for testing only and cannot be stored in ZODB. """ grok.implements(IPayee)