## $Id: interfaces.py 12796 2015-03-19 15:13:27Z henrik $ ## ## 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 ## import decimal from zc.sourcefactory.basic import BasicSourceFactory from zope import schema from zope.component import getUtilitiesFor from zope.component.interfaces import IObjectEvent from zope.interface import Interface, Attribute from waeup.ikoba.interfaces import ( IIkobaObject, SimpleIkobaVocabulary, ContextualDictSourceFactoryBase) from waeup.ikoba.interfaces import MessageFactory as _ from waeup.ikoba.payments.currencies import ISO_4217_CURRENCIES_VOCAB #: Possible states of payments STATE_UNPAID = 1 STATE_AWAITING_USER_CONFIRM = 2 STATE_AWAITING_GATEWAY_CONFIRM = 4 STATE_PAID = 64 STATE_FAILED = 128 payment_states = SimpleIkobaVocabulary( (_('Not yet paid'), STATE_UNPAID), (_('Waiting for user confirm'), STATE_AWAITING_USER_CONFIRM), (_('Waiting for verification'), STATE_AWAITING_GATEWAY_CONFIRM), (_('Paid'), STATE_PAID), (_('Failed'), STATE_FAILED), ) class IPaymentWaitingForGatewayEvent(IObjectEvent): """Fired when a payment starts waiting for verification. """ object = Attribute("""The payment started waiting.""") class IPaymentFinishedEvent(IObjectEvent): """Fired when a payment failed or succeeded. """ object = Attribute("""The payment finished.""") class IPaymentAborted(IObjectEvent): """Fired when a payment was aborted before external transactions. """ object = Attribute("""The payment aborted.""") class PaymentGatewayServicesSource(BasicSourceFactory): """A source that lists available payment services. Suitable for forms etc. Token and value correspond to the name the respective IPaymentGatewayService utility is registered with. """ _services = None @classmethod def services(cls): """Cache the services registered on startup. We assume that services do not change after startup. """ if cls._services is None: cls._services = dict(getUtilitiesFor(IPaymentGatewayService)) return cls._services def getValues(self): """Get payment gateway registration names. """ return sorted(PaymentGatewayServicesSource.services().keys()) def getTitle(self, value): """Get title of the respective service, if it exists. """ service = PaymentGatewayServicesSource.services().get(value, None) if service is not None: return service.title class IPaymentGatewayService(Interface): """A financial gateway service. Any gateway provider might provide several services. For instance payments by credit card, scratch card, bank transfer, etc. An `IPaymentGatewayService` represents one of those services. Payment services are normally registered as a named global utility. """ title = schema.TextLine( title=u'Title', description=u'Human readable name of gateway service.', required=True, ) def create_payment(payer, payable, payee): """Create a payment. For all parameters we expect an object, that implements `IPayer`, `IPayable`, or `IPayee` respectively. If not, then the given objects must be at least adaptable to the respective interface. Therfore you can pass in some `Customer` as long as there is some `IPayer` adapter for `Customer` objects defined. Returns an `IPayment` object. """ def next_step(payment_id): """Returns a payment (as context) and a view name. May result in (None, None). """ def store(payment): """Store `payment` in site. """ class IPaymentGatewayServicesLister(Interface): """A utility that lists all valid payment gateways. This is a subset of the available payment methods, as some might be disabled in some site. Register your own lister in customized sites! """ class PaymentCategorySource(ContextualDictSourceFactoryBase): """A payment category source delivers all categories of payments. """ #: name of dict to deliver from ikoba utils. DICT_NAME = 'PAYMENT_CATEGORIES' class IPaymentsContainer(IIkobaObject): """A container for all kind of payment objects. """ class ICreditCard(Interface): """A credit card. A credit card is connected to a Payer. """ credit_card_id = schema.TextLine( title=u'Internal Credit Card ID', required=True, ) class IPayableFinder(Interface): """Finds payables. For each type of content you want to make payable, you should define an IPayableFinder that can lookup payables in the site. This enables access from payments (which store payable ids only) to arbitrary content objects (for which a payable finder is registered under some name). The other way round (getting an id and other relevant data from any content object) is ensured by IPayable adapters. """ def get_payable_by_id(item_id): """Get an item by its Id, or none. """ class IPayerFinder(Interface): """Finds payers. For each type of content you understand as payer, you should define an IPayerFinder that can lookup payers in the site. This enables access from payments (which store payer ids only) to arbitrary content objects (for which a payer finder is registered under some name. The other way round (getting an id and other relevant data from any content object) is ensured by IPayer adapters. """ def get_payer_by_id(item_id): """Get a payer by its Id, or none. """ class IPaymentItem(Interface): """Something to sell. """ title = schema.TextLine( title=u'Title', description=u'A short title of the good sold.', required=True, default=u'Unnamed' ) amount = schema.Decimal( title=u'Amount', description=u'Total amount, includung any taxes, fees, etc.', required=True, default=decimal.Decimal('0.00'), ) class IPayable(Interface): """Something that can be payed. Designed to serve as adapter. IPayables turn arbitrary content objects into something with a standarized interfaces for use with payments. While currency is important to tell about the amount currency, the total amount is computed on-demand from payment items. """ payable_id = schema.TextLine( title=u'ID of a payable', description=(u'It should be possible to lookup the payable id ' u'by some registered IPayableFinder later on'), required=True, readonly=True, ) title = schema.TextLine( title=u'Title', description=u'A short description of the payed item.', required=True, default=u'', readonly=True, ) currency = schema.Choice( title=u'Currency', source=ISO_4217_CURRENCIES_VOCAB, required=True, default='USD', readonly=True, ) payment_items = schema.Tuple( title=u'Tuple of IPaymentItems.', value_type=schema.Object( title=u'Payment Item', schema=IPaymentItem, ), required=True, default=(), readonly=False, ) class IPayment(IIkobaObject): """A base representation of payments. In a payment, a payer pays someone (the payee) for something, the item to pay. We currently support only the case where one payer pays one payee for one item. The item might include taxes, handling, shipping, etc. As in RL any payment is actually performed by some financial service provider (like paypal, interswitch, etc.), each of which might provide several types of payments (credit card, scratch card, you name it). In Ikoba we call financial service providers 'gateway' and their types of services are handled as gateway types. Therefore PayPal handling a credit card payment is different from PayPal handling a regular PayPal account transaction. A payment can be approve()d, which means the act of paying was really performed. It can also fail for any reason, in which case we mark the payment 'failed'. """ payment_id = schema.TextLine( title=_(u'Payment Identifier'), default=None, required=True, ) title = schema.TextLine( title=_(u'Payment Description'), default=u'', required=True, ) payer_id = schema.TextLine( title=_(u'Payer'), default=None, required=True, ) payee_id = schema.TextLine( title=_(u'Payee'), default=None, required=False, ) payable_id = schema.TextLine( title=_(u'ID of item/good being paid'), default=None, required=False, ) gateway_service = schema.Choice( title=_(u'Payment Gateway'), description=_(u'Payment gateway that handles this transaction.'), source=PaymentGatewayServicesSource(), default=None, required=True, ) state = schema.Choice( title=_(u'Payment State'), default=STATE_UNPAID, vocabulary=payment_states, required=True, ) creation_date = schema.Datetime( title=_(u'Creation Date'), readonly=False, required=False, ) payment_date = schema.Datetime( title=_(u'Payment Date'), required=False, readonly=False, ) amount = schema.Decimal( title=_(u'Amount'), description=_(u'Total amount, includung any taxes, fees, etc.'), required=True, readonly=True, default=decimal.Decimal('0.00'), ) currency = schema.Choice( title=_(u'Currency'), source=ISO_4217_CURRENCIES_VOCAB, required=True, default='USD', ) payment_items = schema.Tuple( title=_(u'Tuple of IPaymentItems'), value_type=schema.Object( title=_(u'Payment Item'), schema=IPaymentItem, ), required=True, default=(), readonly=False, ) def approve(): """Approve a payment. The payment was approved and can now be considered payed. This kind of approvement means the final one (in case there are several instances to ask). """ def mark_failed(reason=None): """Mark the payment as failed. A failed payment was canceled due to technical problems, insufficient funds, etc. """ class IPayer(Interface): """A payer. """ payer_id = schema.TextLine( title=u'Payer ID', required=True, ) first_name = schema.TextLine( title=u'First Name', required=True, ) last_name = schema.TextLine( title=u'Last Name', required=True, ) class IPayee(Interface): """A person or institution being paid. """ payee_id = schema.TextLine( title=u'Payee ID', required=True )