Changeset 12311
- Timestamp:
- 24 Dec 2014, 09:17:05 (10 years ago)
- Location:
- main/waeup.ikoba/trunk
- Files:
-
- 10 edited
- 8 copied
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.ikoba/trunk
- Property svn:mergeinfo changed
/main/waeup.ikoba/branches/uli-payments merged: 12113-12117,12132-12141,12152-12154,12157-12159,12163,12179,12198,12248,12276-12278,12294-12296,12305
- Property svn:mergeinfo changed
-
main/waeup.ikoba/trunk/src/waeup/ikoba
-
main/waeup.ikoba/trunk/src/waeup/ikoba/payments/__init__.py
r11949 r12311 3 3 # Make this a package. 4 4 from waeup.ikoba.payments.container import PaymentsContainer 5 from waeup.ikoba.payments.payment import OnlinePayment5 from waeup.ikoba.payments.payment import Payment 6 6 7 7 __all__ = [ 8 8 'PaymentsContainer', 9 ' OnlinePayment',9 'Payment', 10 10 ] -
main/waeup.ikoba/trunk/src/waeup/ikoba/payments/catalog.py
r12186 r12311 22 22 from waeup.ikoba.payments.interfaces import IPayment 23 23 24 24 25 class PaymentIndexes(grok.Indexes): 25 26 """A catalog for all payments. … … 29 30 grok.context(IPayment) 30 31 31 p_id = grok.index.Field(attribute='p_id') 32 p_category = grok.index.Field(attribute='p_category') 33 p_item = grok.index.Field(attribute='p_item') 34 p_state = grok.index.Field(attribute='p_state') 32 payment_id = grok.index.Field(attribute='payment_id') 33 payer_id = grok.index.Field(attribute='payer_id') 34 payed_item_id = grok.index.Field(attribute='payed_item_id') 35 state = grok.index.Field(attribute='state') 36 amount = grok.index.Field(attribute='amount') -
main/waeup.ikoba/trunk/src/waeup/ikoba/payments/container.py
r11949 r12311 20 20 """ 21 21 import grok 22 from grok import index23 22 from waeup.ikoba.payments.interfaces import IPaymentsContainer 24 23 from waeup.ikoba.utils.helpers import attrs_to_fields 24 25 25 26 26 class PaymentsContainer(grok.Container): -
main/waeup.ikoba/trunk/src/waeup/ikoba/payments/interfaces.py
r12186 r12311 16 16 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 17 ## 18 from zope.interface import Attribute 18 import decimal 19 from zc.sourcefactory.basic import BasicSourceFactory 19 20 from zope import schema 21 from zope.component import getUtilitiesFor 22 from zope.interface import Interface 20 23 from waeup.ikoba.interfaces import ( 21 IIkobaObject, SimpleIkobaVocabulary, 22 ContextualDictSourceFactoryBase) 24 IIkobaObject, SimpleIkobaVocabulary, ContextualDictSourceFactoryBase) 23 25 from waeup.ikoba.interfaces import MessageFactory as _ 26 from waeup.ikoba.payments.currencies import ISO_4217_CURRENCIES_VOCAB 27 28 #: Possible states of payments 29 STATE_UNPAID = 1 30 STATE_PAID = 2 31 STATE_FAILED = 4 24 32 25 33 payment_states = SimpleIkobaVocabulary( 26 (_('Not yet paid'), 'unpaid'),27 (_('Paid'), 'paid'),28 (_('Failed'), 'failed'),34 (_('Not yet paid'), STATE_UNPAID), 35 (_('Paid'), STATE_PAID), 36 (_('Failed'), STATE_FAILED), 29 37 ) 38 39 40 class PaymentGatewayServicesSource(BasicSourceFactory): 41 """A source that lists available payment services. 42 43 Suitable for forms etc. Token and value correspond to the name the 44 respective IPaymentGatewayService utility is registered with. 45 """ 46 47 _services = None 48 49 @classmethod 50 def services(cls): 51 """Cache the services registered on startup. 52 53 We assume that services do not change after startup. 54 """ 55 if cls._services is None: 56 cls._services = dict(getUtilitiesFor(IPaymentGatewayService)) 57 return cls._services 58 59 def getValues(self): 60 """Get payment gateway registration names. 61 """ 62 return sorted(PaymentGatewayServicesSource.services().keys()) 63 64 def getTitle(self, value): 65 """Get title of the respective service, if it exists. 66 """ 67 service = PaymentGatewayServicesSource.services().get(value, None) 68 if service is not None: 69 return service.title 70 71 72 class IPaymentGatewayService(Interface): 73 """A financial gateway service. 74 75 Any gateway provider might provide several services. For instance 76 payments by credit card, scratch card, bank transfer, etc. An 77 `IPaymentGatewayService` represents one of those services. 78 79 Payment services are normally registered as a named global 80 utility. 81 """ 82 title = schema.TextLine( 83 title=u'Title', 84 description=u'Human readable name of gateway service.', 85 required=True, 86 ) 87 88 def create_payment(payer, payment_item, payee): 89 """Create a payment. 90 91 For all parameters we expect an object, that implements 92 `IPayer`, `IPaymentItem`, or `IPayee` respectively. If not, 93 then the given objects must be at least adaptable to the 94 respective interface. 95 96 Therfore you can pass in some `Customer` as long as there is 97 some `IPayer` adapter for `Customer` objects defined. 98 99 Returns an `IPayment` object. 100 """ 101 30 102 31 103 class PaymentCategorySource(ContextualDictSourceFactoryBase): … … 36 108 DICT_NAME = 'PAYMENT_CATEGORIES' 37 109 110 38 111 class IPaymentsContainer(IIkobaObject): 39 112 """A container for all kind of payment objects. … … 41 114 """ 42 115 116 43 117 class IPayment(IIkobaObject): 44 118 """A base representation of payments. 45 119 46 """ 47 p_id = Attribute('Payment identifier') 48 49 p_category = schema.Choice( 50 title = _(u'Payment Category'), 51 #default = u'schoolfee', 52 source = PaymentCategorySource(), 53 required = True, 54 ) 55 56 p_item = schema.TextLine( 57 title = u'', 58 default = None, 59 required = False, 60 ) 61 62 display_item = schema.TextLine( 63 title = _(u'Payment Item'), 64 required = False, 65 readonly = True, 66 ) 67 68 p_state = schema.Choice( 69 title = _(u'Payment State'), 70 default = u'unpaid', 71 vocabulary = payment_states, 72 required = True, 120 In a payment, a payer payes someone (the payee) for something, the 121 item to pay. 122 123 We currently support only the case where one payer pays one payee 124 for one item. The item might include taxes, handling, 125 shipping, etc. 126 127 As in RL any payment is actually performed by some financial 128 service provider (like paypal, interswitch, etc.), each of which 129 might provide several types of payments (credit card, scratch 130 card, you name it). 131 132 In Ikoba we call financial service providers 'gateway' and their 133 types of services are handled as gateway types. Therefore PayPal 134 handling a credit card payment is different from PayPal handling a 135 regular PayPal account transaction. 136 137 A payment can be approve()d, which means the act of paying was 138 really performed. It can also fail for any reason, in which case 139 we mark the payment 'failed'. 140 """ 141 payment_id = schema.TextLine( 142 title=u'Payment Identifier', 143 default=None, 144 required=True, 145 ) 146 147 payer_id = schema.TextLine( 148 title=u'Payer', 149 default=None, 150 required=True, 151 ) 152 153 payed_item_id = schema.TextLine( 154 title=u'Payed Item ID', 155 default=None, 156 required=True, 157 ) 158 159 gateway_service = schema.Choice( 160 title=u'Payment Gateway', 161 description=u'Payment gateway that handles this transaction.', 162 source=PaymentGatewayServicesSource(), 163 default=None, 164 required=True, 165 ) 166 167 state = schema.Choice( 168 title=_(u'Payment State'), 169 default=STATE_UNPAID, 170 vocabulary=payment_states, 171 required=True, 73 172 ) 74 173 75 174 creation_date = schema.Datetime( 76 title = _(u'Ticket Creation Date'),77 readonly =False,78 required =False,175 title=_(u'Creation Datetime'), 176 readonly=False, 177 required=False, 79 178 ) 80 179 81 180 payment_date = schema.Datetime( 82 title = _(u'Payment Date'), 83 required = False, 84 readonly = False, 85 ) 86 87 amount_auth = schema.Float( 88 title = _(u'Amount Authorized'), 89 default = 0.0, 90 required = True, 91 readonly = False, 92 ) 181 title=_(u'Payment Datetime'), 182 required=False, 183 readonly=False, 184 ) 185 186 amount = schema.Decimal( 187 title=_(u'Amount'), 188 description=_( 189 'The overall sum payed, including all taxes fees, etc.'), 190 default=decimal.Decimal("0.00"), 191 required=True, 192 readonly=False, 193 ) 194 195 def approve(): 196 """Approve a payment. 197 198 The payment was approved and can now be considered payed. This 199 kind of approvement means the final one (in case there are 200 several instances to ask). 201 """ 202 203 def mark_failed(reason=None): 204 """Mark the payment as failed. 205 206 A failed payment was canceled due to technical problems, 207 insufficient funds, etc. 208 """ 209 93 210 94 211 class IOnlinePayment(IPayment): … … 98 215 99 216 ac = schema.TextLine( 100 title =_(u'Activation Code'),101 default =None,102 required =False,103 readonly =False,217 title=_(u'Activation Code'), 218 default=None, 219 required=False, 220 readonly=False, 104 221 ) 105 222 106 223 r_amount_approved = schema.Float( 107 title =_(u'Response Amount Approved'),108 default =0.0,109 required =False,110 readonly =False,224 title=_(u'Response Amount Approved'), 225 default=0.0, 226 required=False, 227 readonly=False, 111 228 ) 112 229 113 230 r_code = schema.TextLine( 114 title =_(u'Response Code'),115 default =None,116 required =False,117 readonly =False,231 title=_(u'Response Code'), 232 default=None, 233 required=False, 234 readonly=False, 118 235 ) 119 236 120 237 r_desc = schema.TextLine( 121 title =_(u'Response Description'),122 default =None,123 required =False,124 readonly =False,238 title=_(u'Response Description'), 239 default=None, 240 required=False, 241 readonly=False, 125 242 ) 126 243 127 244 def approve(): 128 245 "Approve an online payment and set to paid." 246 247 248 class ICreditCard(Interface): 249 """A credit card. 250 251 A credit card is connected to a Payer. 252 """ 253 credit_card_id = schema.TextLine( 254 title=u'Internal Credit Card ID', 255 required=True, 256 ) 257 258 259 class IPayer(Interface): 260 """A payer. 261 """ 262 payer_id = schema.TextLine( 263 title=u'Payer ID', 264 required=True, 265 ) 266 267 first_name = schema.TextLine( 268 title=u'First Name', 269 required=False, 270 ) 271 272 last_name = schema.TextLine( 273 title=u'Last Name', 274 required=False, 275 ) 276 277 278 class IPayee(Interface): 279 """A person or institution being paid. 280 """ 281 payee_id = schema.TextLine( 282 title=u'Payee ID', 283 required=True 284 ) 285 286 287 class IPaymentItem(Interface): 288 """Something to sell. 289 """ 290 item_id = schema.TextLine( 291 title=u'Payment Item ID', 292 required=True, 293 ) 294 295 title = schema.TextLine( 296 title=u'Title', 297 description=u'A short title of the good sold.', 298 required=True, 299 default=u'Unnamed' 300 ) 301 302 amount = schema.Decimal( 303 title=u'Amount', 304 description=u'Total amount, includung any taxes, fees, etc.', 305 required=True, 306 default=decimal.Decimal('0.00'), 307 ) 308 309 currency = schema.Choice( 310 title=u'Currency', 311 source=ISO_4217_CURRENCIES_VOCAB, 312 required=True, 313 default='USD', 314 ) -
main/waeup.ikoba/trunk/src/waeup/ikoba/payments/payment.py
r11949 r12311 19 19 These are the payment tickets. 20 20 """ 21 import decimal 21 22 import grok 23 import uuid 22 24 from datetime import datetime 23 from grok import index25 from zope.component import getUtilitiesFor 24 26 from zope.event import notify 25 from zope.component import getUtility26 from zope.i18n import translate27 from waeup.ikoba.interfaces import IIkobaUtils28 from waeup.ikoba.interfaces import MessageFactory as _29 27 from waeup.ikoba.payments.interfaces import ( 30 IPayment, IOnlinePayment,31 payment_states)32 from waeup.ikoba.utils.helpers import attrs_to_fields, get_current_principal 28 IPayment, STATE_UNPAID, STATE_FAILED, STATE_PAID, 29 IPaymentGatewayService, IPayer, 30 ) 33 31 from waeup.ikoba.utils.logger import Logger 34 32 35 class Payment(grok.Container, Logger): 33 34 def get_payment_providers(): 35 """Get all services of payment gateways registered. 36 """ 37 return dict( 38 getUtilitiesFor(IPaymentGatewayService) 39 ) 40 41 42 class PaymentProviderServiceBase(grok.GlobalUtility): 43 44 grok.baseclass() 45 grok.implements(IPaymentGatewayService) 46 47 title = u'Sample Credit Card Service' 48 49 50 class Payment(grok.Model, Logger): 36 51 """This is a payment. 37 52 """ 38 53 grok.implements(IPayment) 39 54 grok.provides(IPayment) 40 grok.baseclass()41 55 42 56 logger_name = 'waeup.ikoba.${sitename}.payments' … … 44 58 logger_format_str = '"%(asctime)s","%(user)s",%(message)s' 45 59 46 def logger_info(self, comment=None):47 """Get the logger's info method.48 """49 self.logger.info('%s' % comment)50 return51 52 60 def __init__(self): 53 61 super(Payment, self).__init__() 54 62 self.creation_date = datetime.utcnow() 55 self.p_id = None 63 self.payment_date = None 64 self.payment_id = u'PAY_' + unicode(uuid.uuid4().hex) 65 self.gateway_service = None 66 self.amount = decimal.Decimal("0.00") 67 self.payed_item_id = None 68 self.payer_id = None 69 self.state = STATE_UNPAID 56 70 return 57 71 58 @property 59 def p_state_title(self): 60 return payment_states.getTermByToken(self.p_state).title 72 def approve(self, payment_date=None): 73 """A payment was approved. 61 74 62 @property 63 def category(self): 64 utils = getUtility(IIkobaUtils) 65 return utils.PAYMENT_CATEGORIES.get(self.p_category, None) 75 Successful ending; the payment is marked as payed. 66 76 67 @property 68 def display_item(self): 69 ikoba_utils = getUtility(IIkobaUtils) 70 return ikoba_utils.getPaymentItem(self) 77 If `payment_date` is given, it must be a datetime object 78 giving a datetime in UTC timezone. 71 79 72 class OnlinePayment(Payment): 73 """This is an online payment. 74 """ 75 grok.implements(IOnlinePayment) 76 grok.provides(IOnlinePayment) 80 Raises ObjectModifiedEvent. 81 """ 82 if payment_date is None: 83 payment_date = datetime.utcnow() 84 self.payment_date = payment_date 85 self.state = STATE_PAID 86 notify(grok.ObjectModifiedEvent(self)) 77 87 78 def __init__(self): 79 super(OnlinePayment, self).__init__() 80 p_id = None 81 return 88 def mark_failed(self, reason=None): 89 """Mark payment as failed. 82 90 83 def approve(self): 84 "Approve online payment and set to paid." 85 self.r_amount_approved = self.amount_auth 86 self.r_code = u'AP' 87 self.p_state = 'paid' 88 user = get_current_principal() 89 if user is None: 90 # in tests 91 usertitle = 'system' 92 else: 93 usertitle = getattr(user, 'public_name', None) 94 if not usertitle: 95 usertitle = user.title 96 r_desc = _('Payment approved by ${a}', mapping = {'a': usertitle}) 97 portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE 98 self.r_desc = translate(r_desc, 'waeup.ikoba', 99 target_language=portal_language) 100 self.payment_date = datetime.utcnow() 101 # Update catalog 91 Raises ObjectModifiedEvent. 92 """ 93 self.state = STATE_FAILED 102 94 notify(grok.ObjectModifiedEvent(self)) 103 return104 105 OnlinePayment = attrs_to_fields(OnlinePayment, omit=['display_item']) -
main/waeup.ikoba/trunk/src/waeup/ikoba/payments/paypal.py
r12060 r12311 18 18 """Support for PayPal payments. 19 19 """ 20 import decimal 21 import grok 20 22 import ConfigParser 23 import inspect 21 24 import paypalrestsdk 25 import re 26 import uuid 27 from zope import schema 22 28 from zope.component import queryUtility 23 from waeup.ikoba.interfaces import IPayPalConfig 29 from zope.interface import Interface, Attribute 30 from waeup.ikoba.interfaces import MessageFactory as _ 31 from waeup.ikoba.interfaces import IPayPalConfig, SimpleIkobaVocabulary 32 from waeup.ikoba.utils.helpers import attrs_to_fields 33 from waeup.ikoba.payments.interfaces import ( 34 IPayment, IPaymentGatewayService, IPayer, IPaymentItem, IPayee, 35 ) 36 from waeup.ikoba.payments.paypal_countries import COUNTRIES_VOCAB 37 from waeup.ikoba.payments.paypal_currencies import CURRENCIES_VOCAB 38 39 #: Intents allowed for paypal based payments 40 PAYMENT_INTENTS = ('sale', 'authorize', 'order') 41 42 #: Payment methods allowed by PayPal 43 PAYMENT_METHODS = ('credit_card', 'paypal') 44 45 #: Payer status set/accepted by PayPal 46 PAYER_STATUS = ('VERIFIED', 'UNVERIFIED') 47 48 #: Tax ID types accepted by PayPal (yes, this list is complete) 49 TAX_ID_TYPES = ('BR_CPF', 'BR_CNPJ') 50 51 #: Address types accepted by PayPal 52 ADDRESS_TYPES = ('residential', 'business', 'mailbox') 53 54 #: A vocabulary with address types 55 ADDRESS_TYPES_VOCAB = SimpleIkobaVocabulary( 56 *[(_(x), x) for x in ADDRESS_TYPES]) 57 58 #: Credit card types accepted by PayPal 59 CREDIT_CARD_TYPES = ('visa', 'mastercard', 'discover', 'amex') 60 61 CREDIT_CARD_TYPES_VOCAB = SimpleIkobaVocabulary( 62 *[(_(x), x) for x in CREDIT_CARD_TYPES]) 63 64 #: Credit card status accepted by PayPal 65 CREDIT_CARD_STATUS = ('expired', 'ok') 66 67 CREDIT_CARD_STATUS_VOCAB = SimpleIkobaVocabulary( 68 *[(_(x), x) for x in CREDIT_CARD_STATUS]) 69 70 #: Stock keeping units we support. 71 STOCK_KEEPING_UNITS = { 72 "pcs": _("pieces"), 73 } 74 75 STOCK_KEEPING_UNITS_VOCAB = SimpleIkobaVocabulary( 76 *[(value, key) for key, value in STOCK_KEEPING_UNITS.items()]) 77 78 #: Payment methods (flags) used by PayPal 79 PAYMENT_OPTION_METHODS = ('INSTANT_FUNDING_SOURCE', ) 80 PAYMENT_OPTION_METHODS_VOCAB = SimpleIkobaVocabulary( 81 *[(_(x), x) for x in PAYMENT_OPTION_METHODS]) 82 83 84 def to_dict(obj, name_map={}): 85 """Turn `obj` into some dict representation. 86 """ 87 result = dict() 88 for name in dir(obj): 89 if name.startswith('_'): 90 continue 91 value = getattr(obj, name) 92 if value is None: 93 continue 94 if inspect.ismethod(value): 95 continue 96 if hasattr(value, 'to_dict'): 97 value = value.to_dict() 98 elif isinstance(value, list): 99 value = [x.to_dict() for x in value] 100 elif isinstance(value, decimal.Decimal): 101 value = u"%.2f" % (value, ) 102 else: 103 value = unicode(value) 104 name = name_map.get(name, name) 105 result[name] = value 106 return result 24 107 25 108 … … 91 174 92 175 93 def get_payment(): 176 class Payer(object): 177 """A payer as required in paypal payments. 178 179 According to Paypal docs: 180 181 `payment_method` must be one of ``'paypal'`` or ``'credit_card'`` 182 as stored in `PAYMENT_METHODS`. 183 184 `funding_instruments` is a list of `FundingInstrument` objects. I 185 think the list must be empty for ``paypal`` payments and must 186 contain at least one entry for ``credit_card`` payments. 187 188 `payer_info` must be provided for ``paypal`` payments and might be 189 provided for ``credit_card`` payments. It's a `PayerInfo` object. 190 191 `status` reflects the payer's PayPal account status and is 192 currently supported for ``paypal`` payments. Allowed values are 193 ``'VERIFIED'`` and ``'UNVERIFIED'`` as stored in `PAYER_STATUS`. 194 """ 195 def __init__(self, payment_method, funding_instruments=[], 196 payer_info=None, status=None): 197 if payment_method not in PAYMENT_METHODS: 198 raise ValueError( 199 "Invalid payment method: use one of %s" % 200 (PAYMENT_METHODS, ) 201 ) 202 if status and status not in PAYER_STATUS: 203 raise ValueError( 204 "Invalid status: use one of %s" % (PAYER_STATUS, ) 205 ) 206 self.payment_method = payment_method 207 self.funding_instruments = funding_instruments 208 self.payer_info = payer_info 209 self.status = status 210 211 212 class PayerInfo(object): 213 """Payer infos as required by Paypal payers. 214 215 Normally used with a `Payer` instance (which in turn is part of a 216 `payment`). 217 218 According to PayPal docs: 219 220 Pre-filled by PayPal when `payment_method` is ``'paypal'``. 221 222 `email`: 127 chars max. I don't think, this value is pre-filled. 223 224 `first_name`: First name of payer. Assigned by PayPal. 225 226 `last_naem`: Last name of payer. Assigned by PayPal. 227 228 `payer_id`: Payer ID as assigned by PayPal. Do not mix up with any 229 Ikoba Payer IDs. 230 231 `phone`: Phone number representing the payer. 20 chars max. 232 233 `shipping_address`: a shipping address object of payer. Assigned 234 by PayPal. 235 236 `tax_id_type`: Payer's tax ID type. Allowed values: ``'BR_CPF'``, 237 ``'BR_CNPJ'`` (I have not the slightest clue what that means). 238 Supported (PayPal-wise) with ``paypal`` payment method only (not 239 with ``credit_card``). 240 241 `tax_id`: Payer's tax ID. Here the same as for `tax_id_type` 242 applies (except that also other values are accepted). 243 244 By default all values are set to the empty string and shipping 245 address to `None`. 246 247 See also: :class:`Payer` 248 """ 249 def __init__(self, email='', first_name='', last_name='', 250 payer_id='', phone='', shipping_address=None, 251 tax_id_type='', tax_id=''): 252 if tax_id_type and tax_id_type not in TAX_ID_TYPES: 253 raise ValueError( 254 "Invalid tax id type: use one of %s" % 255 (TAX_ID_TYPES, ) 256 ) 257 self.email = email 258 self.first_name = first_name 259 self.last_name = last_name 260 self.payer_id = payer_id 261 self.phone = phone 262 self.shipping_address = shipping_address 263 self.tax_id_type = tax_id_type 264 self.tax_id = tax_id 265 266 267 class FundingInstrument(object): 268 """Representation of a payer's funding instrument as required by PayPal. 269 270 Represents always a credit card. Either by a complete set of 271 credit card infos or by a credit card token, which contains only a 272 limited set of credit card data and represents a full set stored 273 in PayPal vault. 274 """ 275 276 def __init__(self, credit_card=None, credit_card_token=None): 277 if credit_card is None and credit_card_token is None: 278 raise ValueError( 279 "Must provide credit card data or a token") 280 if credit_card is not None and credit_card_token is not None: 281 raise ValueError( 282 "Must provide credit card data *or* a token, not both.") 283 self.credit_card = credit_card 284 self.credit_card_token = credit_card_token 285 286 def to_dict(self): 287 return to_dict(self) 288 289 290 class ICreditCard(Interface): 291 """A credit card (full data set) as accepted by PayPal. 292 """ 293 294 paypal_id = schema.TextLine( 295 title=u'PayPal ID', 296 description=u'ID of the credit card provided by PayPal.', 297 required=False, 298 ) 299 300 payer_id = schema.TextLine( 301 title=u'Payer ID', 302 description=(u'A unique identifier for the credit card. This ' 303 u'identifier is provided by Ikoba and must be set ' 304 u'with all future transactions, once put into ' 305 u'PayPal vault.'), 306 required=True, 307 ) 308 309 number = schema.TextLine( 310 title=u'Credit Card Number', 311 description=u'Numeric characters only w/o spaces or punctuation', 312 required=True, 313 ) 314 315 credit_card_type = schema.Choice( 316 title=u'Credit Card Type', 317 description=u'Credit card types supported by PayPal.', 318 required=True, 319 source=CREDIT_CARD_TYPES_VOCAB, 320 ) 321 322 expire_month = schema.Int( 323 title=u'Expiration Month', 324 description=u"Month, the credit card expires.", 325 required=True, 326 default=1, 327 min=1, 328 max=12, 329 ) 330 331 expire_year = schema.Int( 332 title=u'Expiration Year', 333 description=u'4-digit expiration year.', 334 required=True, 335 default=2020, 336 min=1900, 337 max=9999, 338 ) 339 340 cvv2 = schema.TextLine( 341 title=u'CVV2', 342 description=u'3-4 digit card validation code', 343 required=False, 344 ) 345 346 first_name = schema.TextLine( 347 title=u'First Name', 348 description=u"Cardholder's first name.", 349 required=False, 350 ) 351 352 last_name = schema.TextLine( 353 title=u'Last Name', 354 description=u"Cardholder's last name.", 355 required=False, 356 ) 357 358 billing_address = Attribute( 359 "Billing address associated with card.") 360 361 state = schema.Choice( 362 title=u"Status", 363 description=(u"Status of credit card funding instrument. " 364 u"Value assigned by PayPal." 365 ), 366 required=False, 367 source=CREDIT_CARD_STATUS_VOCAB, 368 default=None, 369 ) 370 371 valid_unti = schema.TextLine( 372 title=u'Valid until', 373 description=(u'Funding instrument expiratiopn date, ' 374 u'assigned by PayPal'), 375 required=False, 376 ) 377 378 379 @attrs_to_fields 380 class CreditCard(object): 381 """A credit card (full info set) as used by PayPal. 382 383 Normally used with a `FundingInstrument` instance. 384 385 According to PayPal docs: 386 387 `paypal_id`: provided by PayPal when storing credit card 388 data. Required if using a stored credit card. 389 390 `payer_id`: Unique identifier. If none is given, we assign a 391 uuid. The uuid reads 'PAYER_<32 hex digits>'. 392 393 `number`: Credit card number. Numeric characters only with no 394 spaces or punctuation. The string must conform with modulo 395 and length required by each credit card type. Redacted in 396 responses. Required. 397 398 `credit_card_type`: One of ``'visa'``, ``'mastercard'``, 399 ``'discover'``, ``'amex'``. Required. 400 401 `expire_month`: Expiration month. A number from 1 through 402 12. Required. 403 404 `expire_year`: 4-digit expiration year. Required. 405 406 `cvv2`: 3-4 digit card validation code. 407 408 `first_name`: card holders first name. 409 410 `last_name`: card holders last name. 411 412 `billing_address`: Billing address associated with card. A 413 `Billing` instance. 414 415 `state`: state of the credit card funding instrument. Valid values 416 are ``'expired'`` and ``'ok'``. *Value assigned by PayPal.* 417 418 `paypal_valid_until`: Funding instrument expiration date. 419 *Value assigned by PayPal.* Not to confuse with the credit cards 420 expiration date, which is set via `expire_month` and 421 `expire_year`. 422 423 """ 424 grok.implements(ICreditCard) 425 426 def __init__(self, paypal_id=None, payer_id=None, number=None, 427 credit_card_type=None, expire_month=1, expire_year=2000, 428 cvv2=None, first_name=None, last_name=None, 429 billing_address=None, state=None, paypal_valid_until=None): 430 if not re.match('^[0-9]+$', number): 431 raise ValueError("Credit card number may " 432 "not contain non-numbers.") 433 if payer_id is None: 434 payer_id = u'PAYER_' + unicode(uuid.uuid4().hex) 435 self.paypal_id = paypal_id 436 self.payer_id = payer_id 437 self.number = number 438 self.credit_card_type = credit_card_type 439 self.expire_month = expire_month 440 self.expire_year = expire_year 441 self.cvv2 = cvv2 442 self.first_name = first_name 443 self.last_name = last_name 444 self.billing_address = billing_address 445 self.state = state 446 self.paypal_valid_until = paypal_valid_until 447 448 def to_dict(self): 449 return to_dict(self, name_map={'credit_card_type': 'type'}) 450 451 452 class ICreditCardToken(Interface): 453 """A credit card token corresponding to a credit card stored with PayPal. 454 """ 455 credit_card_id = schema.TextLine( 456 title=u"Credit Card ID", 457 description=u"ID if credit card previously stored with PayPal", 458 required=True, 459 ) 460 461 payer_id = schema.TextLine( 462 title=u'Payer ID', 463 description=(u'A unique identifier for the credit card. This ' 464 u'identifier is provided by Ikoba and must be set ' 465 u'with all future transactions, once put into ' 466 u'PayPal vault.'), 467 required=True, 468 ) 469 470 last4 = schema.TextLine( 471 title=u"Credit Card's last 4 numbers", 472 description=( 473 u"Last four digits of the stored credit card number. " 474 u"Value assigned by PayPal."), 475 required=False, 476 min_length=4, 477 max_length=4 478 ) 479 480 credit_card_type = schema.Choice( 481 title=u'Credit Card Type', 482 description=( 483 u'Credit card type supported by PayPal. Value assigned ' 484 u'by PayPal.'), 485 required=False, 486 source=CREDIT_CARD_TYPES_VOCAB, 487 ) 488 489 expire_month = schema.Int( 490 title=u'Expiration Month', 491 description=u"Month, the credit card expires. Assigned by PayPal.", 492 required=True, 493 default=1, 494 min=1, 495 max=12, 496 ) 497 498 expire_year = schema.Int( 499 title=u'Expiration Year', 500 description=u'4-digit expiration year. Assigned by PayPal.', 501 required=True, 502 default=2020, 503 min=1900, 504 max=9999, 505 ) 506 507 508 class CreditCardToken(object): 509 grok.implements(ICreditCardToken) 510 511 def __init__(self, credit_card_id, payer_id=None, last4=None, 512 credit_card_type=None, expire_month=None, expire_year=None): 513 self.credit_card_id = credit_card_id 514 self.payer_id = payer_id 515 self.last4 = last4 516 self.credit_card_type = credit_card_type 517 self.expire_month = expire_month 518 self.expire_year = expire_year 519 520 def to_dict(self): 521 return to_dict(self, name_map={'credit_card_type': 'type'}) 522 523 524 class IShippingAddress(Interface): 525 """A shipping address as accepted by PayPal. 526 """ 527 recipient_name = schema.TextLine( 528 title=u'Recipient Name', 529 required=True, 530 description=u'Name of the recipient at this address.', 531 max_length=50, 532 ) 533 534 type = schema.Choice( 535 title=u'Address Type', 536 description=u'Address Type.', 537 required=False, 538 source=ADDRESS_TYPES_VOCAB, 539 ) 540 541 line1 = schema.TextLine( 542 title=u'Address Line 1', 543 required=True, 544 description=u'Line 1 of the address (e.g., Number, street, etc.)', 545 max_length=100, 546 ) 547 548 line2 = schema.TextLine( 549 title=u'Address Line 2', 550 required=False, 551 description=u'Line 2 of the address (e.g., Suite, apt #, etc.)', 552 max_length=100, 553 ) 554 555 city = schema.TextLine( 556 title=u'City', 557 required=True, 558 description=u'City name', 559 max_length=50, 560 ) 561 562 country_code = schema.Choice( 563 title=u'Country', 564 required=True, 565 description=u'2-letter country code', 566 source=COUNTRIES_VOCAB, 567 ) 568 569 postal_code = schema.TextLine( 570 title=u'Postal code', 571 required=False, 572 description=(u'Zip code or equivalent is usually required ' 573 u'for countries that have them.'), 574 max_length=20, 575 ) 576 577 state = schema.TextLine( 578 title=u'State', 579 required=False, 580 description=(u'2-letter code for US stated, and the ' 581 u'equivalent for other countries.'), 582 max_length=100, 583 ) 584 585 phone = schema.TextLine( 586 title=u'Phone', 587 required=False, 588 description=u'Phone number in E.123 format.', 589 max_length=50, 590 ) 591 592 593 @attrs_to_fields 594 class ShippingAddress(object): 595 """A shipping address as used in PayPal transactions. 596 """ 597 grok.implements(IShippingAddress) 598 599 def __init__(self, recipient_name, type='residential', line1='', 600 line2=None, city='', country_code=None, 601 postal_code=None, state=None, phone=None): 602 self.recipient_name = recipient_name 603 self.type = type 604 self.line1 = line1 605 self.line2 = line2 606 self.city = city 607 self.country_code = country_code 608 self.postal_code = postal_code 609 self.state = state 610 self.phone = phone 611 612 def to_dict(self): 613 return to_dict(self) 614 615 616 class IAddress(Interface): 617 """An address as accepted by PayPal. 618 """ 619 line1 = schema.TextLine( 620 title=u'Address Line 1', 621 required=True, 622 description=u'Line 1 of the address (e.g., Number, street, etc.)', 623 max_length=100, 624 ) 625 626 line2 = schema.TextLine( 627 title=u'Address Line 2', 628 required=False, 629 description=u'Line 2 of the address (e.g., Suite, apt #, etc.)', 630 max_length=100, 631 ) 632 633 city = schema.TextLine( 634 title=u'City', 635 required=True, 636 description=u'City name', 637 max_length=50, 638 ) 639 640 country_code = schema.Choice( 641 title=u'Country', 642 required=True, 643 description=u'2-letter country code', 644 source=COUNTRIES_VOCAB, 645 ) 646 647 postal_code = schema.TextLine( 648 title=u'Postal code', 649 required=False, 650 description=(u'Zip code or equivalent is usually required ' 651 u'for countries that have them.'), 652 max_length=20, 653 ) 654 655 state = schema.TextLine( 656 title=u'State', 657 required=False, 658 description=(u'2-letter code for US stated, and the ' 659 u'equivalent for other countries.'), 660 max_length=100, 661 ) 662 663 phone = schema.TextLine( 664 title=u'Phone', 665 required=False, 666 description=u'Phone number in E.123 format.', 667 max_length=50, 668 ) 669 670 671 @attrs_to_fields 672 class Address(object): 673 """A postal address as used in PayPal transactions. 674 """ 675 grok.implements(IAddress) 676 677 def __init__(self, line1='', line2=None, city='', country_code=None, 678 postal_code=None, state=None, phone=None): 679 self.line1 = line1 680 self.line2 = line2 681 self.city = city 682 self.country_code = country_code 683 self.postal_code = postal_code 684 self.state = state 685 self.phone = phone 686 687 def to_dict(self): 688 """Turn Adress into a dict that can be fed to PayPal classes. 689 """ 690 return to_dict(self) 691 692 693 class AmountDetails(object): 694 """Amount details can be given with Amount objects. 695 696 All parameters are passed in as decimals (`decimal.Decimal`). 697 698 All numbers stored here, might have 10 characters max with 699 support for two decimal places. 700 701 No parameter is strictly required, except `subtotal`, which must 702 be set if any of the other values is set. 703 704 `shipping`: Amount charged for shipping. 705 706 `subtotal`: Amount for subtotal of the items. Automatically 707 computed. If no other item was set, subtotal is `None`. 708 709 `tax`: Amount charged for tax. 710 711 `fee`: Fee charged by PayPal. In case of a refund, this is the fee 712 amount refunded to the original recipient of the payment. Value 713 assigned by PayPal. 714 715 `handling_fee`: Amount being charged for the handling 716 fee. Currently supported with paypal payment_method only, but 717 available for credit_card payment_method at a later date. 718 719 `insurance`: Amount being charged for the insurance fee. Currently 720 supported with paypal payment_method only, but available for 721 credit_card payment_method at a later date. 722 723 `shipping_discount`: Amount being discounted for the shipping 724 fee. Currently supported with paypal payment_method only, but 725 available for credit_card payment_method at a later date. 726 """ 727 def __init__(self, shipping=None, tax=None, fee=None, 728 handling_fee=None, insurance=None, shipping_discount=None): 729 self.shipping = shipping 730 self.tax = tax 731 self.fee = fee 732 self.handling_fee = handling_fee 733 self.insurance = insurance 734 self.shipping_discount = shipping_discount 735 736 @property 737 def subtotal(self): 738 names = ( 739 'shipping', 'tax', 'fee', 'handling_fee', 'insurance', 740 'shipping_discount' 741 ) 742 result = None 743 for name in names: 744 val = getattr(self, name) 745 if name == 'shipping_discount' and val is not None: 746 val = -val 747 if val is not None: 748 if result is None: 749 result = val 750 else: 751 result += val 752 return result 753 754 def to_dict(self): 755 return to_dict(self) 756 757 758 class IAmount(Interface): 759 """An amount as used by PayPal in payments. 760 """ 761 currency = schema.Choice( 762 title=u'Currency', 763 description=u'PayPal does not support all currencies. Required.', 764 required=True, 765 source=CURRENCIES_VOCAB, 766 default=u'USD', 767 ) 768 769 total = schema.Decimal( 770 title=u'Total', 771 description=( 772 u'Total amount charged from the payer to the payee. ' 773 u'In case of a refund, this is the refunded amount to ' 774 u'the original payer from the payee.'), 775 required=True, 776 default=decimal.Decimal("0.00"), 777 max=decimal.Decimal("9999999.99") 778 ) 779 780 details = Attribute( 781 """Additional details related to a payment amount. 782 """) 783 784 785 @attrs_to_fields 786 class Amount(object): 787 grok.implements(IAmount) 788 789 def __init__(self, currency="USD", total=decimal.Decimal("0.00"), 790 details=None): 791 self.currency = currency 792 self.total = total 793 self.details = details 794 795 def to_dict(self): 796 return to_dict(self) 797 798 799 class IItem(Interface): 800 """PayPal Item. 801 802 Items in a PayPal context are goods sold to customers. 803 """ 804 quantity = schema.Int( 805 title=u"Quantity", 806 description=u"Number of this particular items.", 807 required=True, 808 default=1, 809 max=9999999999, 810 ) 811 812 name = schema.TextLine( 813 title=u"Name", 814 description=u"Item name", 815 required=True, 816 max_length=127, 817 ) 818 819 price = schema.Decimal( 820 title=u"Price", 821 description=u"Price", 822 required=True, 823 max=decimal.Decimal("9999999.99"), 824 ) 825 826 currency = schema.Choice( 827 title=u"Currency", 828 description=u"Currency", 829 source=CURRENCIES_VOCAB, 830 default=u'USD', 831 required=True, 832 ) 833 834 sku = schema.Choice( 835 title=u"SKU", 836 description=u"Stock keeping unit corresponding to item.", 837 source=STOCK_KEEPING_UNITS_VOCAB, 838 required=False, 839 ) 840 841 description = schema.TextLine( 842 title=u"Description", 843 description=( 844 u"Description of Item. Currently supported with paypal " 845 u"payments only." 846 ), 847 max_length=127, 848 required=False, 849 ) 850 851 tax = schema.Decimal( 852 title=u"Tax", 853 description=( 854 u"Tax of the item. Currently supported with paypal " 855 u"payments only." 856 ), 857 required=False, 858 ) 859 860 861 @attrs_to_fields 862 class Item(object): 863 """See IItem for docs. 864 """ 865 grok.implements(IItem) 866 867 def __init__(self, name, quantity=1, price=decimal.Decimal("0.00"), 868 currency="USD", sku=None, description=None, tax=None): 869 self.name = name 870 self.quantity = quantity 871 self.price = price 872 self.currency = currency 873 self.sku = sku 874 self.description = description 875 self.tax = tax 876 877 def to_dict(self): 878 return to_dict(self) 879 880 881 class IItemList(Interface): 882 """List of `Item` objects and a related `ShippingAddress`. 883 884 `items`: can be a simple list of `Item` objects. 885 886 `shipping_address`: a `ShippingAddress` object. Only needed if 887 different from Payer address. 888 """ 889 items = schema.List( 890 title=u"Items", 891 description=u"PayPal Items are sold goods", 892 value_type=schema.Object( 893 title=u"Item", 894 description=u"Item in a list", 895 schema=IItem, 896 ) 897 ) 898 899 shipping_address = schema.Object( 900 title=u"Shipping Address", 901 description=u"Shipping address of receiver if different from payee.", 902 schema=IShippingAddress, 903 required=False, 904 ) 905 906 907 @attrs_to_fields 908 class ItemList(object): 909 """List of `Item` objects and a related `ShippingAddress`. 910 911 `items`: can be a simple list of `Item` objects. 912 913 `shipping_address`: a `ShippingAddress` object. Only needed if 914 different from Payer address. 915 """ 916 grok.implements(IItemList) 917 918 def __init__(self, items=[], shipping_address=None): 919 self.items = items 920 self.shipping_address = shipping_address 921 922 def to_dict(self): 923 return to_dict(self) 924 925 926 class IPaymentOptions(Interface): 927 """Payment options requested for a certain purchase unit. 928 929 `allowed_payment_method`: Optional payment method type. If 930 specified, the transaction will go through for only instant 931 payment. Allowed values: ``INSTANT_FUNDING_SOURCE``. Only for 932 use with the ``paypal`` payment_method, not relevant for the 933 ``credit_card`` payment_method. 934 """ 935 allowed_payment_method = schema.Choice( 936 title=u"Allowed payment method", 937 description=( 938 u"Optional payment type. If specified, the transaction " 939 u"will go through for only instant payment. Only for use " 940 u"with paypal payment method, not relevant for credit cards." 941 ), 942 required=False, 943 source=PAYMENT_OPTION_METHODS_VOCAB, 944 ) 945 946 947 @attrs_to_fields 948 class PaymentOptions(object): 949 """Payment options requested for a certain purchase unit. 950 """ 951 grok.implements(IPaymentOptions) 952 953 def __init__(self, allowed_payment_method=None): 954 if allowed_payment_method not in ( 955 None, 'INSTANT_FUNDING_SOURCE'): 956 raise ValueError( 957 "allowed_payment_method of PaymentOptions must be None or " 958 "'INSTANT_FUNDING_SOURCE'" 959 ) 960 self.allowed_payment_method = allowed_payment_method 961 962 def to_dict(self): 963 return to_dict(self) 964 965 966 class ITransaction(Interface): 967 """PayPal transactions provide payment transaction details. 968 """ 969 amount = schema.Object( 970 title=u"Amount", 971 description=u"Amount being collected.", 972 schema=IAmount, 973 required=True, 974 ) 975 976 description = schema.TextLine( 977 title=u"Description", 978 description=u"Description of transaction", 979 required=False, 980 max_length=127, 981 ) 982 983 item_list = schema.Object( 984 title=u"Item List", 985 description=u"List of items", 986 required=False, 987 schema=IItemList, 988 ) 989 990 # XXX: This should be defined more precisely: What kind of objects, etc. 991 # PayPal docs say: "array of sale, authorization, capture, or refund, 992 # objects" 993 related_resources = Attribute("Arbitrary objects") 994 995 invoice_number = schema.TextLine( 996 title=u"Invoice Number", 997 description=( 998 u"Invoice number used to track the payment. " 999 u"Currently supported with paypal payment_method only." 1000 ), 1001 required=False, 1002 max_length=256, 1003 ) 1004 1005 custom = schema.TextLine( 1006 title=u"Custom text", 1007 description=( 1008 u"Free-form field for the use of clients. Currently " 1009 u"supported with paypal payment_method only."), 1010 required=False, 1011 max_length=256, 1012 ) 1013 1014 soft_descriptor = schema.TextLine( 1015 title=u"Soft descriptor", 1016 description=( 1017 u"Soft descriptor used when charging this funding " 1018 u"source. Currently supported with paypal payment_method only" 1019 ), 1020 required=False, 1021 max_length=22, 1022 ) 1023 1024 payment_options = schema.Object( 1025 title=u"Payment Options", 1026 description=u"Payment options requested for this purchase unit.", 1027 required=False, 1028 schema=IPaymentOptions, 1029 ) 1030 1031 1032 @attrs_to_fields 1033 class Transaction(object): 1034 # See ITransaction for description 1035 1036 grok.implements(ITransaction) 1037 1038 def __init__(self, amount, description=None, item_list=None, 1039 related_resources=[], invoice_number=None, custom=None, 1040 soft_descriptor=None, payment_options=None): 1041 self.amount = amount 1042 self.description = description 1043 self.item_list = item_list 1044 self.related_resources = related_resources 1045 self.invoice_number = invoice_number 1046 self.custom = custom 1047 self.soft_descriptor = soft_descriptor 1048 self.payment_options = payment_options 1049 1050 def to_dict(self): 1051 """Give a `dict` representation of this `Transaction`. 1052 """ 1053 return to_dict(self) 1054 1055 1056 def get_payment(intent='sale', payment_method='credit_card'): 94 1057 """Construct a payment. 95 1058 96 1059 You have to `create()` the payment yourself. 97 1060 1061 Returns a paypalrestsdk Payment object, not an Ikoba payment. 1062 1063 As `intent` currently only the string ``'sale'`` is supported. 1064 98 1065 XXX: Just some sampledata yet. 99 1066 """ 1067 if intent != "sale": 1068 raise ValueError( 1069 "Currently, only 'sale' is allowed as type of paypal" 1070 "payment.") 100 1071 payment = paypalrestsdk.Payment( 101 1072 { 102 "intent": "sale",1073 "intent": intent, 103 1074 "payer": { 104 1075 "payment_method": "credit_card", … … 123 1094 ]}, 124 1095 "transactions": [{ 125 126 127 128 129 130 131 132 133 134 1096 "amount": { 1097 "total": "7.47", 1098 "currency": "USD", 1099 "details": { 1100 "subtotal": "7.41", 1101 "tax": "0.03", 1102 "shipping": "0.03"}}, 1103 "description": ("This is the payment " 1104 "transaction description.") 1105 }] 135 1106 } 136 1107 ) 137 1108 return payment 1109 1110 1111 class IPayPalPayment(IPayment): 1112 """A paypal payment. 1113 """ 1114 1115 1116 class PayPalPayment(grok.Model): 1117 """A paypal payment. 1118 """ 1119 pass 1120 1121 1122 class PayPalCreditCardService(grok.GlobalUtility): 1123 grok.implements(IPaymentGatewayService) 1124 grok.name('paypal_creditcard') 1125 1126 title = _(u'Credit Card (PayPal)') 1127 1128 def create_payment(self, payer, payment_item, payee=None): 1129 if not IPayer.providedBy(payer): 1130 payer = IPayer(payer) 1131 if not IPaymentItem.providedBy(payment_item): 1132 payment_item = IPaymentItem(payment_item) 1133 return None 1134 payment = get_payment() 1135 return payment 1136 1137 1138 class PayPalRegularPaymentService(grok.GlobalUtility): 1139 grok.implements(IPaymentGatewayService) 1140 grok.name('paypal_regular') 1141 1142 title = _(u'PayPal Payment') -
main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_interfaces.py
r12060 r12311 17 17 ## 18 18 """ 19 Tests for payments .19 Tests for payments interfaces. 20 20 """ 21 from zope.interface.verify import verifyClass, verifyObject 22 from waeup.ikoba.payments.interfaces import ( 23 IPaymentsContainer, IOnlinePayment) 24 from waeup.ikoba.payments.container import PaymentsContainer 25 from waeup.ikoba.payments.payment import OnlinePayment 21 from waeup.ikoba.payments.interfaces import PaymentGatewayServicesSource 26 22 from waeup.ikoba.testing import (FunctionalLayer, FunctionalTestCase) 27 23 … … 31 27 layer = FunctionalLayer 32 28 33 def test_interfaces(self): 34 # Make sure the correct interfaces are implemented. 35 self.assertTrue( 36 verifyClass( 37 IPaymentsContainer, PaymentsContainer) 38 ) 39 self.assertTrue( 40 verifyObject( 41 IPaymentsContainer, PaymentsContainer()) 42 ) 43 self.assertTrue( 44 verifyClass( 45 IOnlinePayment, OnlinePayment) 46 ) 47 self.assertTrue( 48 verifyObject( 49 IOnlinePayment, OnlinePayment()) 50 ) 51 return 29 def test_payment_gateway_services_source(self): 30 # the payment gateway services source provides a list of registered 31 # payment gateways 32 source = PaymentGatewayServicesSource() 33 services = list(source) 34 assert len(services) > 0 52 35 53 def test_ base(self):54 # We cannot call the fundamental methods of a base in that case55 container = PaymentsContainer()56 se lf.assertRaises(57 NotImplementedError, container.archive)58 self.assertRaises(59 NotImplementedError, container.clear)36 def test_payment_gateway_services_source_title(self): 37 # we can get titles from gateway sources 38 source = PaymentGatewayServicesSource() 39 service1 = list(source)[0] 40 title = source.factory.getTitle(service1) 41 assert title != service1 42 assert isinstance(title, basestring) -
main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_paypal.py
r12060 r12311 16 16 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 17 ## 18 import decimal 18 19 import os 20 import re 19 21 import shutil 20 22 import tempfile 21 23 import unittest 22 24 import paypalrestsdk 23 from zope.component import getGlobalSiteManager, queryUtility 25 from zope.component import ( 26 getGlobalSiteManager, queryUtility, getUtilitiesFor, 27 ) 28 from zope.i18nmessageid.message import Message as i18nMessage 29 from zope.interface import implements 30 from zope.interface.verify import verifyObject, verifyClass 31 from zope.schema.interfaces import IVocabularyTokenized, ITerm 24 32 from waeup.ikoba.interfaces import IPayPalConfig 33 from waeup.ikoba.payments.interfaces import ( 34 IPaymentGatewayService, IPayer, IPayee, IPaymentItem, IPayment, 35 ) 25 36 from waeup.ikoba.payments.paypal import ( 26 37 get_paypal_config_file_path, parse_paypal_config, get_access_token, 27 configure_sdk, get_payment, 38 configure_sdk, get_payment, Payer, PayerInfo, ShippingAddress, 39 ADDRESS_TYPES_VOCAB, IShippingAddress, Address, IAddress, to_dict, 40 CreditCard, CREDIT_CARD_TYPES_VOCAB, CREDIT_CARD_STATUS_VOCAB, 41 ICreditCard, ICreditCardToken, CreditCardToken, FundingInstrument, 42 AmountDetails, IAmount, Amount, IItem, Item, STOCK_KEEPING_UNITS_VOCAB, 43 IItemList, ItemList, IPaymentOptions, PaymentOptions, ITransaction, 44 Transaction, PAYMENT_OPTION_METHODS_VOCAB, PayPalCreditCardService, 45 PayPalRegularPaymentService, 28 46 ) 29 47 from waeup.ikoba.testing import ( 30 48 FunctionalLayer, FunctionalTestCase, 31 49 ) 50 32 51 33 52 # … … 118 137 assert paypalrestsdk.api.__api__.client_id == 'my-special-id' 119 138 139 def test_get_payment_invalid_intent(self): 140 # only 'sale' is currently allowed 141 self.assertRaises( 142 ValueError, get_payment, intent='invalid') 143 self.assertRaises( 144 ValueError, get_payment, intent='order') 145 146 def test_to_dict(self): 147 # we can turn objects into dicts 148 class C1(object): 149 a = 1 150 b = "somestring" 151 c = u"unicodestring" 152 d = None 153 obj = C1() 154 self.assertEqual( 155 to_dict(obj), 156 {'a': u"1", 157 'b': u"somestring", 158 'c': u"unicodestring", 159 } 160 ) 161 162 def test_to_dict_map(self): 163 # we can map attribute names 164 class C1(object): 165 a = 1 166 b = "somestring" 167 obj = C1() 168 self.assertEqual( 169 to_dict(obj, name_map={'a': 'replaced_a'}), 170 {'replaced_a': u"1", 171 'b': u"somestring", 172 }) 173 174 def test_to_dict_lists(self): 175 # to_dict can handle lists 176 class C1(object): 177 a = 1 178 179 def to_dict(self): 180 return to_dict(self) 181 182 obj1 = C1() 183 obj2 = C1() 184 obj2.a = 2 185 obj3 = C1() 186 obj3.a = 3 187 obj1.foo = [obj2, obj3] 188 self.assertEqual( 189 to_dict(obj1), 190 { 191 'a': u'1', 192 'foo': [ 193 {'a': u'2'}, 194 {'a': u'3'}, 195 ] 196 } 197 ) 198 199 def test_to_dict_decimals(self): 200 # decimals are converted to numbers with 2 decimals 201 class C1(object): 202 a = decimal.Decimal("0.1") 203 self.assertEqual( 204 to_dict(C1()), 205 { 206 'a': u"0.10", 207 } 208 ) 209 210 211 class PayerTests(unittest.TestCase): 212 213 def test_create(self): 214 # we can create payer objects 215 payer = Payer(payment_method='paypal') 216 assert payer.payment_method == 'paypal' 217 assert payer.funding_instruments == [] 218 assert payer.payer_info is None 219 assert payer.status is None 220 221 def test_init_invalid_payment_meth(self): 222 # we must provide a valid payment method 223 payer = Payer(payment_method='paypal') 224 assert payer is not None 225 payer = Payer(payment_method='credit_card') 226 assert payer is not None 227 self.assertRaises( 228 ValueError, Payer, payment_method='invalid') 229 230 def test_init_invalid_payer_state(self): 231 # only certain values are allowed as payer states 232 payer = Payer(payment_method='paypal', status='VERIFIED') 233 assert payer is not None 234 payer = Payer(payment_method='paypal', status='UNVERIFIED') 235 assert payer is not None 236 self.assertRaises( 237 ValueError, Payer, payment_method='paypal', status='InVaLiD') 238 239 240 class PayerInfoTests(unittest.TestCase): 241 242 def test_create(self): 243 # we can create payer infos 244 info = PayerInfo() 245 assert info.email == '' 246 assert info.first_name == '' 247 assert info.last_name == '' 248 assert info.payer_id == '' 249 assert info.phone == '' 250 assert info.shipping_address is None 251 assert info.tax_id_type == '' 252 assert info.tax_id == '' 253 254 def test_init_invalid_tax_id_type(self): 255 # onyl certain tax id types are allowed 256 info = PayerInfo(tax_id_type='BR_CPF') 257 assert info is not None 258 info = PayerInfo(tax_id_type='BR_CNPJ') 259 assert info is not None 260 self.assertRaises( 261 ValueError, PayerInfo, tax_id_type='INVALID_TYPE') 262 263 264 class CreditCardTests(unittest.TestCase): 265 266 def test_iface(self): 267 # we fullfill any interface contracts 268 credit_card = CreditCard( 269 number=u"12345678", 270 credit_card_type="visa", 271 expire_month=4, 272 expire_year=2012, 273 ) 274 verifyClass(ICreditCard, CreditCard) 275 verifyObject(ICreditCard, credit_card) 276 277 def test_create(self): 278 # we can create CreditCard objects 279 credit_card = CreditCard( 280 number=u"12345678", 281 credit_card_type="visa", 282 expire_month=4, 283 expire_year=2012, 284 ) 285 assert credit_card.paypal_id is None 286 assert credit_card.payer_id is not None 287 assert credit_card.number == u"12345678" 288 assert credit_card.credit_card_type == "visa" 289 assert credit_card.expire_month == 4 290 assert credit_card.expire_year == 2012 291 assert credit_card.cvv2 is None 292 assert credit_card.first_name is None 293 assert credit_card.last_name is None 294 assert credit_card.billing_address is None 295 assert credit_card.state is None 296 assert credit_card.paypal_valid_until is None 297 298 def test_payer_id_given(self): 299 # we do not override given payer ids 300 credit_card = CreditCard( 301 number=u"12345678", 302 credit_card_type="visa", 303 expire_month=4, 304 expire_year=2012, 305 payer_id=u'MySpecialPayerId', 306 ) 307 assert credit_card.payer_id == u'MySpecialPayerId' 308 309 def test_payer_id_not_given(self): 310 # in case no payer id is given, we generate one 311 credit_card = CreditCard( 312 number=u"12345678", 313 credit_card_type="visa", 314 expire_month=4, 315 expire_year=2012, 316 ) 317 # our payer ids contain a leading 'PAYER_' and 32 hex digits 318 assert re.match('PAYER_[0-9a-f]{32}$', credit_card.payer_id) 319 320 def test_number_is_checked(self): 321 # we do not accept invalid numbers 322 self.assertRaises( 323 ValueError, CreditCard, 324 number=u"not-a-number", 325 credit_card_type="visa", 326 expire_month=4, 327 expire_year=2012, 328 ) 329 330 def test_to_str(self): 331 # we can turn CreditCard objects into dicts. 332 addr = Address( 333 line1=u"52 N Main ST", 334 city=u"Johnstown", 335 state=u"OH", 336 postal_code=u"43210", 337 country_code=u"US") 338 credit_card = CreditCard( 339 credit_card_type=u"visa", 340 payer_id=u"PAYER_0123456789012345678901", 341 number=u"4417119669820331", 342 expire_month=11, 343 expire_year=2018, 344 cvv2=u"874", 345 first_name=u"Joe", 346 last_name=u"Shopper", 347 billing_address=addr) 348 self.assertEqual( 349 credit_card.to_dict(), 350 { 351 "type": u"visa", 352 "number": u"4417119669820331", 353 "payer_id": u"PAYER_0123456789012345678901", 354 "expire_month": u"11", 355 "expire_year": u"2018", 356 "cvv2": u"874", 357 "first_name": u"Joe", 358 "last_name": u"Shopper", 359 "billing_address": { 360 "line1": u"52 N Main ST", 361 "city": u"Johnstown", 362 "state": u"OH", 363 "postal_code": u"43210", 364 "country_code": u"US"} 365 } 366 ) 367 368 369 class CreditCardTokenTests(unittest.TestCase): 370 371 def test_iface(self): 372 # we fullfill any interface contracts 373 token = CreditCardToken( 374 credit_card_id=u"12345678", 375 ) 376 verifyClass(ICreditCardToken, CreditCardToken) 377 verifyObject(ICreditCardToken, token) 378 379 def test_create(self): 380 # we can create CreditCardToken objects 381 token = CreditCardToken( 382 credit_card_id=u"12345678", 383 ) 384 assert token.credit_card_id == u"12345678" 385 assert token.payer_id is None 386 assert token.credit_card_type is None 387 assert token.expire_month is None 388 assert token.expire_year is None 389 assert token.last4 is None 390 391 def test_payer_id_given(self): 392 # we do not override given payer ids 393 token = CreditCardToken( 394 credit_card_id=u"12345678", 395 payer_id=u'MySpecialPayerId', 396 ) 397 assert token.payer_id == u'MySpecialPayerId' 398 399 def test_to_str(self): 400 # we can turn CreditCardToken objects into dicts. 401 token = CreditCardToken( 402 credit_card_type=u"visa", 403 payer_id=u"PAYER_0123456789012345678901", 404 credit_card_id=u"12345678", 405 last4="8901", 406 expire_month=11, 407 expire_year=2018, 408 ) 409 self.assertEqual( 410 token.to_dict(), 411 { 412 "credit_card_id": u"12345678", 413 "payer_id": u"PAYER_0123456789012345678901", 414 "last4": u"8901", 415 "type": u"visa", 416 "expire_month": u"11", 417 "expire_year": u"2018", 418 } 419 ) 420 421 422 class FundingInstrumentTests(unittest.TestCase): 423 424 def test_create(self): 425 # we can create FundingInstrument objects 426 token = CreditCardToken( 427 credit_card_id=u"12345678", 428 ) 429 instr = FundingInstrument(credit_card_token=token) 430 assert instr.credit_card_token is token 431 432 def test_require_credit_card_or_token(self): 433 # we require a credit card object or a token. 434 credit_card = CreditCard( 435 number=u"12345678", 436 credit_card_type="visa", 437 expire_month=4, 438 expire_year=2012, 439 ) 440 token = CreditCardToken( 441 credit_card_id=u"12345678", 442 ) 443 self.assertRaises( 444 ValueError, FundingInstrument, 445 credit_card=credit_card, 446 credit_card_token=token 447 ) 448 self.assertRaises( 449 ValueError, FundingInstrument 450 ) 451 452 def test_to_dict(self): 453 # we can turn Funding instruments into dicts 454 token = CreditCardToken( 455 credit_card_type=u"visa", 456 payer_id=u"PAYER_0123456789012345678901", 457 credit_card_id=u"12345678", 458 last4="8901", 459 expire_month=11, 460 expire_year=2018, 461 ) 462 instr = FundingInstrument(credit_card_token=token) 463 result = instr.to_dict() 464 self.assertEqual( 465 result, 466 { 467 "credit_card_token": { 468 "credit_card_id": u"12345678", 469 "payer_id": u"PAYER_0123456789012345678901", 470 "last4": u"8901", 471 "type": u"visa", 472 "expire_month": u"11", 473 "expire_year": u"2018", 474 } 475 } 476 ) 477 478 479 class AddressTests(unittest.TestCase): 480 481 def test_iface(self): 482 # we fullfill any interface contracts 483 addr = Address( 484 line1=u'Address Line 1', 485 city=u'Somecity', 486 country_code=u'AT', 487 ) 488 verifyClass(IAddress, Address) 489 verifyObject(IAddress, addr) 490 491 def test_create(self): 492 # we can create addresses 493 addr = Address( 494 line1=u'Honey Street 1', 495 city=u'Beartown', 496 country_code=u'GB', 497 ) 498 assert addr.line1 == u'Honey Street 1' 499 assert addr.line2 is None 500 assert addr.city == u'Beartown' 501 assert addr.country_code == u'GB' 502 assert addr.postal_code is None 503 assert addr.state is None 504 assert addr.phone is None 505 506 def test_to_dict(self): 507 # we can turn addresses into dicts 508 addr = Address( 509 line1=u'Honey Street 1', 510 city=u'Beartown', 511 country_code=u'GB', 512 ) 513 self.assertEqual( 514 addr.to_dict(), 515 { 516 'line1': u'Honey Street 1', 517 'city': u'Beartown', 518 'country_code': u'GB', 519 } 520 ) 521 addr.line2 = u"Behind little tree" 522 self.assertEqual( 523 addr.to_dict(), 524 { 525 'line1': u'Honey Street 1', 526 'line2': u'Behind little tree', 527 'city': u'Beartown', 528 'country_code': u'GB', 529 } 530 ) 531 532 533 class ShippingAddressTests(unittest.TestCase): 534 535 def test_iface(self): 536 # we fullfill any interface contracts 537 addr = ShippingAddress( 538 recipient_name=u'Foo Bar', 539 line1=u'Address Line 1', 540 city=u'Somecity', 541 country_code=u'AT', 542 ) 543 verifyClass(IShippingAddress, ShippingAddress) 544 verifyObject(IShippingAddress, addr) 545 546 def test_create(self): 547 # we can create shipping addresses 548 addr = ShippingAddress( 549 recipient_name=u'Rob Receiver', 550 line1=u'Honey Street 1', 551 city=u'Beartown', 552 country_code=u'GB', 553 ) 554 assert addr.recipient_name == u'Rob Receiver' 555 assert addr.type == u'residential' 556 assert addr.line1 == u'Honey Street 1' 557 assert addr.line2 is None 558 assert addr.city == u'Beartown' 559 assert addr.country_code == u'GB' 560 assert addr.postal_code is None 561 assert addr.state is None 562 assert addr.phone is None 563 564 def test_to_dict(self): 565 # we can turn shipping addresses into dicts 566 addr = ShippingAddress( 567 recipient_name=u'Rob Receiver', 568 line1=u'Honey Street 1', 569 city=u'Beartown', 570 country_code=u'GB', 571 ) 572 self.assertEqual( 573 addr.to_dict(), 574 { 575 'recipient_name': u'Rob Receiver', 576 'type': u'residential', 577 'line1': u'Honey Street 1', 578 'city': u'Beartown', 579 'country_code': u'GB', 580 } 581 ) 582 addr.line2 = u"Behind little tree" 583 self.assertEqual( 584 addr.to_dict(), 585 { 586 'recipient_name': u'Rob Receiver', 587 'type': u'residential', 588 'line1': u'Honey Street 1', 589 'line2': u'Behind little tree', 590 'city': u'Beartown', 591 'country_code': u'GB', 592 } 593 ) 594 595 596 class AmountDetailsTests(unittest.TestCase): 597 598 def test_create(self): 599 # we can create AmountDetail objects 600 details = AmountDetails() 601 assert details.shipping is None 602 assert details.subtotal is None 603 assert details.tax is None 604 assert details.fee is None 605 assert details.handling_fee is None 606 assert details.insurance is None 607 assert details.shipping_discount is None 608 609 def test_to_dict(self): 610 # we can turn AmountDetails into a dict 611 details = AmountDetails( 612 shipping=decimal.Decimal("0.10"), 613 tax=decimal.Decimal("0.30"), 614 fee=decimal.Decimal("0.40"), 615 handling_fee=decimal.Decimal("0.50"), 616 insurance=decimal.Decimal("0.60"), 617 shipping_discount=decimal.Decimal("0.70") 618 ) 619 self.assertEqual( 620 details.to_dict(), 621 { 622 'shipping': u"0.10", 623 'subtotal': u"1.20", 624 'tax': u"0.30", 625 'fee': u"0.40", 626 'handling_fee': u"0.50", 627 'insurance': u"0.60", 628 'shipping_discount': u"0.70" 629 } 630 ) 631 632 def test_subtotal_all_none(self): 633 # if all items are none, also subtotal is none 634 details = AmountDetails( 635 shipping=None, tax=None, fee=None, handling_fee=None, 636 insurance=None, shipping_discount=None, 637 ) 638 assert details.subtotal is None 639 details.shipping_discount = decimal.Decimal("1.00") 640 assert details.subtotal == decimal.Decimal("-1.00") 641 642 def test_subtotal_sum(self): 643 # subtotal sums up correctly 644 details = AmountDetails( 645 shipping=decimal.Decimal("0.05"), 646 tax=decimal.Decimal("0.40"), 647 fee=decimal.Decimal("3.00"), 648 handling_fee=decimal.Decimal("20.00"), 649 insurance=decimal.Decimal("100.00"), 650 shipping_discount=None 651 ) 652 self.assertEqual(details.subtotal, decimal.Decimal("123.45")) 653 details.shipping_discount = decimal.Decimal("0.00") 654 self.assertEqual(details.subtotal, decimal.Decimal("123.45")) 655 details.shipping_discount = decimal.Decimal("23.45") 656 self.assertEqual(details.subtotal, decimal.Decimal("100.00")) 657 658 659 class AmountTests(unittest.TestCase): 660 661 def test_iface(self): 662 # we fullfill any interface contracts. 663 amount = Amount() 664 verifyClass(IAmount, Amount) 665 verifyObject(IAmount, amount) 666 667 def test_create(self): 668 # we can create amount objects 669 details = AmountDetails( 670 shipping=decimal.Decimal("0.05"), 671 tax=decimal.Decimal("0.40"), 672 fee=decimal.Decimal("3.00"), 673 handling_fee=decimal.Decimal("20.00"), 674 insurance=decimal.Decimal("100.00"), 675 shipping_discount=None 676 ) 677 amount = Amount( 678 total=decimal.Decimal("12.12"), 679 currency="USD", 680 details=details 681 ) 682 assert amount.total == decimal.Decimal("12.12") 683 assert amount.currency == "USD" 684 assert amount.details is details 685 686 def test_to_dict(self): 687 # we can turn Amount objects into dicts 688 self.maxDiff = None 689 details = AmountDetails( 690 shipping=decimal.Decimal("0.05"), 691 tax=decimal.Decimal("0.40"), 692 fee=decimal.Decimal("3.00"), 693 handling_fee=decimal.Decimal("20.00"), 694 insurance=decimal.Decimal("100.00"), 695 shipping_discount=None 696 ) 697 amount = Amount( 698 total=decimal.Decimal("12.12"), 699 currency="USD", 700 details=details 701 ) 702 self.assertEqual( 703 amount.to_dict(), 704 { 705 'total': u'12.12', 706 'currency': u'USD', 707 'details': { 708 'shipping': u'0.05', 709 'subtotal': u'123.45', 710 'tax': u'0.40', 711 'fee': u'3.00', 712 'handling_fee': u'20.00', 713 'insurance': u'100.00', 714 } 715 } 716 ) 717 718 719 class ItemTests(unittest.TestCase): 720 721 def test_iface(self): 722 # we fullfill all interface contracts 723 item = Item(name=u"Splendid Item") 724 verifyClass(IItem, Item) 725 verifyObject(IItem, item) 726 727 def test_create(self): 728 # we can create Item objects 729 item = Item( 730 quantity=3, 731 name=u"Splendid Thing", 732 price=decimal.Decimal("1.1"), 733 currency="USD", 734 sku="pcs", 735 description=u"Soo splendid!", 736 tax=decimal.Decimal("0.1"), 737 ) 738 assert item.quantity == 3 739 assert item.name == u"Splendid Thing" 740 assert item.price == decimal.Decimal("1.1") 741 assert item.currency == "USD" 742 assert item.sku == "pcs" 743 assert item.description == u"Soo splendid!" 744 assert item.tax == decimal.Decimal("0.1") 745 746 def test_to_dict(self): 747 # we can turn Item objects into dicts 748 item = Item( 749 quantity=3, 750 name=u"Splendid Thing", 751 price=decimal.Decimal("1.1"), 752 currency="USD", 753 sku="pcs", 754 description=u"Soo splendid!", 755 tax=decimal.Decimal("0.1"), 756 ) 757 self.assertEqual( 758 item.to_dict(), 759 { 760 "quantity": u"3", 761 "name": u"Splendid Thing", 762 "price": u"1.10", 763 "currency": u"USD", 764 "sku": u"pcs", 765 "description": u"Soo splendid!", 766 "tax": u"0.10" 767 } 768 ) 769 770 771 class ItemListTests(unittest.TestCase): 772 773 def test_iface(self): 774 # we fullfill all interface contracts 775 item_list = ItemList() 776 verifyClass(IItemList, ItemList) 777 verifyObject(IItemList, item_list) 778 779 def test_create_minimal(self): 780 # we can create ItemLists with a minimum of params 781 item_list = ItemList() 782 assert item_list.shipping_address is None 783 assert item_list.items == [] 784 785 def test_create(self): 786 # we can create ItemLists 787 item1 = Item( 788 name=u"Splendid Thing", 789 ) 790 item2 = Item( 791 name=u"Other Splendid Thing", 792 ) 793 addr = ShippingAddress( 794 recipient_name=u'Rob Receiver', 795 line1=u'Honey Street 1', 796 city=u'Beartown', 797 country_code=u'GB', 798 ) 799 item_list = ItemList( 800 shipping_address=addr, 801 items=[item1, item2]) 802 assert item_list.shipping_address is addr 803 assert item_list.items == [item1, item2] 804 805 def test_to_dict(self): 806 # we can turn ITemLists into dicts 807 item = Item( 808 quantity=3, 809 name=u"Splendid Thing", 810 price=decimal.Decimal("1.1"), 811 currency="USD", 812 sku="pcs", 813 description=u"Soo splendid!", 814 tax=decimal.Decimal("0.1"), 815 ) 816 addr = ShippingAddress( 817 recipient_name=u'Rob Receiver', 818 line1=u'Honey Street 1', 819 city=u'Beartown', 820 country_code=u'GB', 821 ) 822 item_list = ItemList(items=[item, ], shipping_address=addr) 823 self.assertEqual( 824 item_list.to_dict(), 825 { 826 "items": [ 827 { 828 "quantity": u"3", 829 "name": u"Splendid Thing", 830 "price": u"1.10", 831 "currency": u"USD", 832 "sku": u"pcs", 833 "description": u"Soo splendid!", 834 "tax": u"0.10" 835 } 836 ], 837 "shipping_address": 838 { 839 'recipient_name': u'Rob Receiver', 840 'type': u'residential', 841 'line1': u'Honey Street 1', 842 'city': u'Beartown', 843 'country_code': u'GB', 844 } 845 } 846 ) 847 848 849 class PaymentOptionsTests(unittest.TestCase): 850 851 def test_iface(self): 852 # we fullfill all interface contracts 853 opts = PaymentOptions() 854 verifyClass(IPaymentOptions, PaymentOptions) 855 verifyObject(IPaymentOptions, opts) 856 857 def test_create(self): 858 # we can create PaymentOptions objects 859 opts = PaymentOptions() 860 assert opts.allowed_payment_method is None 861 862 def test_allowed_payment_method_checked_in_init(self): 863 # any value apart from None, INSTANT... is rejected in __init__ 864 self.assertRaises( 865 ValueError, 866 PaymentOptions, allowed_payment_method='NoTvAlID') 867 868 def test_to_dict(self): 869 # we can turn PaymentOptions into dicts 870 opts = PaymentOptions( 871 allowed_payment_method="INSTANT_FUNDING_SOURCE") 872 self.assertEqual( 873 opts.to_dict(), 874 { 875 'allowed_payment_method': "INSTANT_FUNDING_SOURCE", 876 } 877 ) 878 879 880 class TransactionTests(unittest.TestCase): 881 882 def test_iface(self): 883 # we fullfill all interface contracts 884 amount = Amount() 885 transaction = Transaction(amount=amount) 886 verifyClass(ITransaction, Transaction) 887 verifyObject(ITransaction, transaction) 888 889 def test_create(self): 890 # we can create transacions 891 amount = Amount() 892 transaction = Transaction(amount=amount) 893 assert transaction.amount is amount 894 assert transaction.description is None 895 assert transaction.item_list is None 896 assert transaction.related_resources == [] 897 assert transaction.invoice_number is None 898 assert transaction.custom is None 899 assert transaction.soft_descriptor is None 900 assert transaction.payment_options is None 901 902 def test_to_dict(self): 903 # we can turn Transaction objects into dicts 904 transaction = Transaction( 905 amount=Amount(), 906 description=u"My description", 907 item_list=ItemList(), 908 related_resources=[], 909 invoice_number=u"12345", 910 custom=u"Some custom remark", 911 soft_descriptor=u"softdescriptor?", 912 payment_options=PaymentOptions( 913 allowed_payment_method="INSTANT_FUNDING_SOURCE"), 914 ) 915 self.assertEqual( 916 transaction.to_dict(), { 917 'amount': {'currency': u'USD', 'total': u'0.00'}, 918 'custom': u'Some custom remark', 919 'description': u'My description', 920 'invoice_number': u'12345', 921 'item_list': { 922 'items': [] 923 }, 924 'payment_options': { 925 'allowed_payment_method': u'INSTANT_FUNDING_SOURCE' 926 }, 927 'related_resources': [], 928 'soft_descriptor': u'softdescriptor?' 929 } 930 ) 931 932 933 class AddressTypesVocabTests(unittest.TestCase): 934 935 def test_address_types_vocab_tokenized(self): 936 # we can get a countries source suitable for forms etc. 937 verifyObject(IVocabularyTokenized, ADDRESS_TYPES_VOCAB) 938 939 def test_address_types_vocab_i18nized(self): 940 # vocab titles are i18nized 941 result = ADDRESS_TYPES_VOCAB.getTerm('residential') 942 assert ITerm.providedBy(result) 943 self.assertEqual(result.title, u'residential') 944 assert isinstance(result.title, i18nMessage) 945 946 def test_address_types_vocab_tokens_are_string(self): 947 # vocab tokens are simple strings 948 result = ADDRESS_TYPES_VOCAB.getTerm('residential') 949 assert ITerm.providedBy(result) 950 assert result.token == result.value 951 assert result.value == 'residential' 952 assert isinstance(result.token, str) 953 assert isinstance(result.value, str) 954 955 956 class CreditCardTypesVocabTests(unittest.TestCase): 957 958 def test_credit_card_types_vocab_tokenized(self): 959 # we can get a countries source suitable for forms etc. 960 verifyObject(IVocabularyTokenized, CREDIT_CARD_TYPES_VOCAB) 961 962 def test_credit_cards_types_vocab_i18nized(self): 963 # vocab titles are i18nized 964 result = CREDIT_CARD_TYPES_VOCAB.getTerm('visa') 965 assert ITerm.providedBy(result) 966 self.assertEqual(result.title, u'visa') 967 assert isinstance(result.title, i18nMessage) 968 969 def test_credit_cards_types_vocab_tokens_are_string(self): 970 # vocab tokens are simple strings 971 result = CREDIT_CARD_TYPES_VOCAB.getTerm('visa') 972 assert ITerm.providedBy(result) 973 assert result.token == result.value 974 assert result.value == 'visa' 975 assert isinstance(result.token, str) 976 assert isinstance(result.value, str) 977 978 979 class CreditcardstatusVocabTests(unittest.TestCase): 980 981 def test_credit_card_status_vocab_tokenized(self): 982 # we can get a countries source suitable for forms etc. 983 verifyObject(IVocabularyTokenized, CREDIT_CARD_STATUS_VOCAB) 984 985 def test_credit_cards_status_vocab_i18nized(self): 986 # vocab titles are i18nized 987 result = CREDIT_CARD_STATUS_VOCAB.getTerm('ok') 988 assert ITerm.providedBy(result) 989 self.assertEqual(result.title, u'ok') 990 assert isinstance(result.title, i18nMessage) 991 992 def test_credit_cards_status_vocab_tokens_are_string(self): 993 # vocab tokens are simple strings 994 result = CREDIT_CARD_STATUS_VOCAB.getTerm('expired') 995 assert ITerm.providedBy(result) 996 assert result.token == result.value 997 assert result.value == 'expired' 998 assert isinstance(result.token, str) 999 assert isinstance(result.value, str) 1000 1001 1002 class StockKeepingUnitsVocabTests(unittest.TestCase): 1003 1004 def test_sku_vocab_tokenized(self): 1005 # we can get a tokenzed vocab for stock keeping units 1006 verifyObject(IVocabularyTokenized, STOCK_KEEPING_UNITS_VOCAB) 1007 1008 def test_sku_vocab_i18nized(self): 1009 # vocab titles are i18nized 1010 result = STOCK_KEEPING_UNITS_VOCAB.getTerm('pcs') 1011 assert ITerm.providedBy(result) 1012 self.assertEqual(result.title, u'pieces') 1013 assert isinstance(result.title, i18nMessage) 1014 1015 def test_sku_vocab_tokens_are_string(self): 1016 # vocab tokens are simple strings 1017 result = STOCK_KEEPING_UNITS_VOCAB.getTerm('pcs') 1018 assert ITerm.providedBy(result) 1019 assert result.token == result.value 1020 assert result.value == 'pcs' 1021 assert isinstance(result.token, str) 1022 assert isinstance(result.value, str) 1023 1024 1025 class PaymentOptionMethodsVocabTests(unittest.TestCase): 1026 1027 def test_payment_option_methods_vocab_tokenized(self): 1028 # we can get a countries source suitable for forms etc. 1029 verifyObject(IVocabularyTokenized, PAYMENT_OPTION_METHODS_VOCAB) 1030 1031 def test_payment_option_methods_vocab_i18nized(self): 1032 # vocab titles are i18nized 1033 result = PAYMENT_OPTION_METHODS_VOCAB.getTerm('INSTANT_FUNDING_SOURCE') 1034 assert ITerm.providedBy(result) 1035 self.assertEqual(result.title, u'INSTANT_FUNDING_SOURCE') 1036 assert isinstance(result.title, i18nMessage) 1037 1038 def test_payment_option_methods_vocab_tokens_are_string(self): 1039 # vocab tokens are simple strings 1040 result = PAYMENT_OPTION_METHODS_VOCAB.getTerm('INSTANT_FUNDING_SOURCE') 1041 assert ITerm.providedBy(result) 1042 assert result.token == result.value 1043 assert result.value == 'INSTANT_FUNDING_SOURCE' 1044 assert isinstance(result.token, str) 1045 assert isinstance(result.value, str) 1046 1047 1048 class FakePayer(object): 1049 1050 implements(IPayer) 1051 1052 payer_id = 'PAYER-123' 1053 1054 1055 class FakePayee(object): 1056 1057 implements(IPayee) 1058 1059 payee_id = 'PAYEE-456' 1060 1061 1062 class FakePaymentItem(object): 1063 1064 implements(IPaymentItem) 1065 1066 item_id = 'BILL-123456' 1067 amount = decimal.Decimal("12.10") 1068 currency = 'EUR' 1069 1070 1071 class PayPalCreditCardServiceTests(unittest.TestCase): 1072 1073 def test_iface(self): 1074 # we fullfill all interface contracts 1075 service = PayPalCreditCardService() 1076 verifyClass(IPaymentGatewayService, PayPalCreditCardService) 1077 verifyObject(IPaymentGatewayService, service) 1078 1079 def DIStest_creditcard_service_can_create_payment(self): 1080 # we can create IPayment objects with creditcard service 1081 service = PayPalCreditCardService() 1082 payment = service.create_payment( 1083 payer=FakePayer(), 1084 payment_item=FakePaymentItem(), 1085 payee=FakePayee() 1086 ) 1087 assert IPayment.providedBy(payment) 1088 120 1089 121 1090 class FunctionalPaypalTests(FunctionalTestCase): … … 136 1105 result = payment.create() 137 1106 assert result is True 1107 1108 def test_paypal_services_registered(self): 1109 # the PayPal gateway services are all registered 1110 creditcard_service = queryUtility( 1111 IPaymentGatewayService, name=u'paypal_creditcard') 1112 assert creditcard_service is not None 1113 assert isinstance(creditcard_service, PayPalCreditCardService) 1114 paypal_regular_service = queryUtility( 1115 IPaymentGatewayService, name=u'paypal_regular') 1116 assert paypal_regular_service is not None 1117 assert isinstance(paypal_regular_service, PayPalRegularPaymentService)
Note: See TracChangeset for help on using the changeset viewer.