Changeset 12734 for main/waeup.ikoba/branches/uli-payments/src
- Timestamp:
- 11 Mar 2015, 17:08:34 (10 years ago)
- Location:
- main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba
- Files:
-
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/browser.py
r12724 r12734 43 43 from waeup.ikoba.payments.payment import format_payment_item_values 44 44 from waeup.ikoba.payments.interfaces import ( 45 IPaymentGatewayServicesLister, IPaymentGatewayService, IPayer 45 IPaymentGatewayServicesLister, IPaymentGatewayService, IPayer, IPayable 46 46 ) 47 47 from waeup.ikoba.widgets.hrefwidget import HREFDisplayWidget … … 56 56 from waeup.ikoba.customers.catalog import search 57 57 from waeup.ikoba.customers.workflow import PAYMENT_TRANSITIONS 58 from waeup.ikoba.customers.contracts import payment_items_from_contract59 58 60 59 … … 1528 1527 return 1529 1528 payer = IPayer(self.context) 1530 payment_items = payment_items_from_contract(self.context) 1531 payment = service.create_payment(payer, payment_items) 1532 payment.payable_id = self.context.contract_id 1529 1530 payable = IPayable(self.context) 1531 payment = service.create_payment(payer, payable) 1532 service.store(payment) 1533 1533 payment, view_name = service.next_step(payment.payment_id) 1534 1534 url = self.url(payment, view_name) -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/contracts.py
r12728 r12734 286 286 @property 287 287 def title(self): 288 return self.context.title 288 return self.context.title or u'' 289 289 290 290 @property -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/demo_provider.py
r12723 r12734 2 2 from waeup.ikoba.interfaces import MessageFactory as _ 3 3 from waeup.ikoba.browser.layout import IkobaEditFormPage, action 4 from waeup.ikoba.payments.interfaces import IPaymentGatewayService, IPayment 4 from waeup.ikoba.payments.interfaces import ( 5 IPaymentGatewayService, IPayment, IPayable, IPayer, IPayee,) 5 6 from waeup.ikoba.payments.payment import ( 6 Payment, get_payment, find_payable_from_payable_id) 7 Payment, get_payment, find_payable_from_payable_id, 8 PaymentProviderServiceBase) 7 9 8 10 … … 10 12 11 13 12 class DemoCreditcardPaymentService( grok.GlobalUtility):14 class DemoCreditcardPaymentService(PaymentProviderServiceBase): 13 15 """A demo payment gateway service. 14 16 … … 20 22 title = _(u'Credit Card (Demo Payments)') 21 23 22 def create_payment(self, payer, pay ment_item_list=[], payee=None):24 def create_payment(self, payer, payable, payee=None): 23 25 """Create a payment. 24 26 """ 25 payment = Payment() 27 if not IPayer.providedBy(payer): 28 payer = IPayer(payer) 29 if not IPayable.providedBy(payable): 30 payable = IPayable(payable) 31 if (payee is not None) and (not IPayee.providedBy(payee)): 32 payee = IPayee(payee) 33 payment = Payment(payer, payable, payee) 26 34 payment.gateway_service = 'demo_creditcard' # must be grok.name above 27 payment.payer_id = payer.payer_id28 # XXX: we should not have to store a payment before adding items29 site = grok.getSite()30 site['payments'][payment.payment_id] = payment31 for item in payment_item_list:32 payment.add_payment_item(item)33 35 return payment 34 36 -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/interfaces.py
r12726 r12734 20 20 from zope import schema 21 21 from zope.component import getUtilitiesFor 22 from zope.container.interfaces import IContainer23 from zope.container.constraints import contains24 22 from zope.interface import Interface, Attribute 25 23 from waeup.ikoba.interfaces import ( … … 88 86 ) 89 87 90 def create_payment(payer, pay ment_item_list, payee):88 def create_payment(payer, payable, payee): 91 89 """Create a payment. 92 90 93 91 For all parameters we expect an object, that implements 94 `IPayer`, `IPay mentItem`, or `IPayee` respectively. If not,92 `IPayer`, `IPayable`, or `IPayee` respectively. If not, 95 93 then the given objects must be at least adaptable to the 96 94 respective interface. … … 106 104 107 105 May result in (None, None). 106 """ 107 108 def store(payment): 109 """Store `payment` in site. 108 110 """ 109 111 … … 232 234 233 235 234 class IPayment(I Container, IIkobaObject):236 class IPayment(IIkobaObject): 235 237 """A base representation of payments. 236 238 … … 256 258 we mark the payment 'failed'. 257 259 """ 258 contains(IPaymentItem)259 260 260 payment_id = schema.TextLine( 261 261 title=u'Payment Identifier', 262 262 default=None, 263 required=True, 264 ) 265 266 title = schema.TextLine( 267 title=u'Payment description.', 268 default=u'', 263 269 required=True, 264 270 ) … … 333 339 """ 334 340 335 def add_payment_item(item):336 """Payments contain payment items.337 338 Add one339 """340 341 341 342 342 class IPayer(Interface): -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/payment.py
r12721 r12734 101 101 102 102 class PaymentProviderServiceBase(grok.GlobalUtility): 103 103 """Base for IPaymentGatewayServices. 104 """ 104 105 grok.baseclass() 105 106 grok.implements(IPaymentGatewayService) … … 107 108 title = u'Sample Credit Card Service' 108 109 109 110 @attrs_to_fields 111 class Payment(grok.Container, Logger): 110 def store(self, payment): 111 """Store `payment` in site. 112 """ 113 site = grok.getSite() 114 payments = site['payments'] 115 if payment.payment_id in payments: 116 del site['payments'][payment.payment_id] 117 site['payments'][payment.payment_id] = payment 118 119 120 @attrs_to_fields 121 class Payment(grok.Model, Logger): 112 122 """This is a payment. 113 123 """ … … 119 129 logger_format_str = '"%(asctime)s","%(user)s",%(message)s' 120 130 121 @property 122 def amount(self): 123 """The amount of a payment. 124 125 Equals the sum of items contained. 126 """ 127 return sum( 128 [item.amount for item in self.values()], 129 decimal.Decimal("0.00") # default value 130 ) 131 132 def __init__(self): 131 def __init__(self, payer, payable, payee=None): 133 132 super(Payment, self).__init__() 133 item_amounts = [decimal.Decimal("0.00"), ] 134 item_amounts += [item.amount for item in payable.payment_items] 135 self.amount = sum(item_amounts) 136 self.payer_id = payer.payer_id 137 self.payable_id = payable.payable_id 138 self.title = payable.title 134 139 self.creation_date = datetime.utcnow() 135 140 self.payment_date = None 136 141 self.payment_id = u'PAY_' + unicode(uuid.uuid4().hex) 137 142 self.state = STATE_UNPAID 143 self.currency = payable.currency 144 if payee is not None: 145 self.payee_id = payee.payee_id 138 146 return 139 147 … … 162 170 notify(grok.ObjectModifiedEvent(self)) 163 171 164 def add_payment_item(self, item):165 """Add `item`166 167 Returns the key under which the `item` was stored. Please do168 not make anby assumptions about the key. It will be a169 string. That is all we can tell.170 171 """172 cnt = 0173 while str(cnt) in self:174 cnt += 1175 self[str(cnt)] = item176 return str(cnt)177 178 172 179 173 @attrs_to_fields … … 187 181 188 182 @attrs_to_fields 189 class PaymentItem( grok.Model):183 class PaymentItem(object): 190 184 191 185 grok.implements(IPaymentItem) 192 186 193 def __init__(self): 187 def __init__( 188 self, item_id=u"0", title=u"", amount=decimal.Decimal("0.00")): 194 189 super(PaymentItem, self).__init__() 190 self.item_id = item_id 191 self.title = title 192 self.amount = amount 195 193 196 194 -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/paypal.py
r12724 r12734 35 35 IPayment, IPaymentGatewayService, IPayer, IPaymentItem, IPayee, 36 36 ) 37 from waeup.ikoba.payments.payment import PaymentProviderServiceBase 37 38 from waeup.ikoba.payments.paypal_countries import COUNTRIES_VOCAB 38 39 from waeup.ikoba.payments.paypal_currencies import CURRENCIES_VOCAB … … 1141 1142 1142 1143 1143 class PayPalCreditCardService( grok.GlobalUtility):1144 class PayPalCreditCardService(PaymentProviderServiceBase): 1144 1145 grok.implements(IPaymentGatewayService) 1145 1146 grok.name('paypal_creditcard') … … 1220 1221 1221 1222 1222 class PayPalRegularPaymentService( grok.GlobalUtility):1223 class PayPalRegularPaymentService(PaymentProviderServiceBase): 1223 1224 grok.implements(IPaymentGatewayService) 1224 1225 grok.name('paypal_regular') -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_demo_provider.py
r12712 r12734 1 import decimal2 1 from zope.component import queryUtility 3 2 from zope.component.hooks import setSite … … 10 9 ) 11 10 from waeup.ikoba.app import Company 12 from waeup.ikoba.payments.payment import Payer, Pay mentItem, Payee, Payment11 from waeup.ikoba.payments.payment import Payer, Payee, Payment 13 12 from waeup.ikoba.payments.demo_provider import ( 14 13 DemoCreditcardPaymentService, 15 14 ) 15 from waeup.ikoba.payments.tests.test_payment import FakePayer, FakePayable 16 16 17 17 … … 35 35 # we can get payments from payment gateways 36 36 service = DemoCreditcardPaymentService() 37 payer, pay ment_item, payee = Payer(), PaymentItem(), Payee()37 payer, payable, payee = Payer(), FakePayable(), Payee() 38 38 payer.payer_id = u'SOME_PAYER_ID' 39 result = service.create_payment(payer, [], payee)39 result = service.create_payment(payer, payable, payee) 40 40 assert IPayment.providedBy(result) 41 41 assert result.gateway_service == u'demo_creditcard' 42 42 assert result.state == STATE_UNPAID 43 assert len(result) == 0 # no items stored44 45 def test_create_payment_honors_payment_item(self):46 # we inspect payment items and take their values47 service = DemoCreditcardPaymentService()48 payer, payment_item, payee = Payer(), PaymentItem(), Payee()49 payment_item.item_id = u'SOME_ITEM_ID'50 payer.payer_id = u'SOME_PAYER_ID'51 payment_item.amount = decimal.Decimal("300.99")52 result = service.create_payment(payer, [payment_item], payee)53 self.assertEqual(result.amount, payment_item.amount)54 assert len(result) == 155 43 56 44 def test_create_payment_honors_payer(self): 57 45 # we inspect payers when creating their payments 58 46 service = DemoCreditcardPaymentService() 59 payer, payment_item, payee = Payer(), PaymentItem(), Payee() 60 payment_item.item_id = u'SOME_ITEM_ID' 47 payer, payable, payee = Payer(), FakePayable(), Payee() 61 48 payer.payer_id = u'SOME_PAYER_ID' 62 result = service.create_payment(payer, [payment_item], payee)49 result = service.create_payment(payer, payable, payee) 63 50 assert result.payer_id == payer.payer_id 64 51 … … 78 65 # we are redirected to 'index' in the beginning 79 66 service = DemoCreditcardPaymentService() 80 p1 = Payment( )67 p1 = Payment(FakePayer(), FakePayable()) 81 68 self.app['payments']['1'] = p1 82 69 p_id = p1.payment_id -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_payment.py
r12719 r12734 24 24 ) 25 25 from zope.component.hooks import setSite 26 from zope.interface import implements 26 from zope.interface import implements, implementer 27 27 from zope.interface.verify import verifyClass, verifyObject 28 28 from waeup.ikoba.payments.interfaces import ( 29 29 IPayment, STATE_UNPAID, STATE_PAID, STATE_FAILED, 30 30 IPaymentGatewayService, IPaymentItem, IPaymentGatewayServicesLister, 31 IPayableFinder, 31 IPayableFinder, IPayable, IPayer, 32 32 ) 33 33 from waeup.ikoba.app import Company … … 37 37 ) 38 38 from waeup.ikoba.testing import (FunctionalLayer, FunctionalTestCase) 39 40 41 @implementer(IPayer) 42 class FakePayer(object): 43 44 def __init__( 45 self, payer_id=u'PAYER_01', first_name=u'Anna', last_name='Tester'): 46 self.payer_id = payer_id 47 self.first_name = first_name 48 self.last_name = last_name 49 50 51 FAKE_PAYMENT_ITEMS = ( 52 PaymentItem(u'ITEM1', u'Item title 1', decimal.Decimal("1.00")), 53 PaymentItem(u'ITEM2', u'Item title 2', decimal.Decimal("2.2")), 54 ) 55 56 57 @implementer(IPayable) 58 class FakePayable(object): 59 60 payable_id = u'id1' 61 items = ( 62 (u'item 1', decimal.Decimal("1.00")), 63 (u'item 2', decimal.Decimal("2.12")), 64 ) 65 66 def __init__(self, payable_id=u'PAYABLE_01', title=u'title', 67 currency=u'USD', payment_items=FAKE_PAYMENT_ITEMS): 68 self.payable_id = payable_id 69 self.title = title 70 self.currency = currency 71 self.payment_items = payment_items 39 72 40 73 … … 103 136 assert len(util()) > 0 104 137 105 def test_add_payment_item(self):106 # we can add payment items107 p1 = Payment()108 item1 = PaymentItem()109 result = p1.add_payment_item(item1)110 assert len(p1) == 1 # do not make assumptions about result content111 assert isinstance(result, basestring)112 113 138 def test_get_payment(self): 114 139 # we can lookup payments. … … 116 141 app = self.getRootFolder()['app'] 117 142 setSite(app) 118 p1 = Payment() 119 item1 = PaymentItem() 143 p1 = Payment(FakePayer(), FakePayable()) 120 144 app['payments']['1'] = p1 121 p1.add_payment_item(item1)122 145 p_id = p1.payment_id 123 146 result = get_payment(p_id) … … 155 178 156 179 157 class PaymentTests(unittest.TestCase): 180 class PaymentTests(FunctionalTestCase): 181 182 layer = FunctionalLayer 183 184 def setUp(self): 185 super(PaymentTests, self).setUp() 186 self.payer = FakePayer() 187 self.payable = FakePayable() 158 188 159 189 def test_iface(self): 160 190 # Payments fullfill any interface contracts 161 obj = Payment( )191 obj = Payment(self.payer, self.payable) 162 192 verifyClass(IPayment, Payment) 163 193 verifyObject(IPayment, obj) 164 194 195 def test_initial_values(self): 196 # important attributes are set initially 197 payer = self.payer 198 payer.payer_id = u'PAYER_ID' 199 payable = self.payable 200 payable.payable_id = u'PAYABLE_ID' 201 payable.title = u'PAYABLE-TITLE' 202 payable.currency = 'NGN' 203 payment = Payment(payer, payable) 204 assert payment.payer_id == u'PAYER_ID' 205 assert payment.payable_id == u'PAYABLE_ID' 206 assert payment.title == u'PAYABLE-TITLE' 207 assert payment.currency == 'NGN' 208 assert isinstance(payment.creation_date, datetime.datetime) 209 assert payment.payment_date is None 210 165 211 def test_payment_id_unique(self): 166 212 # we get unique payment ids 167 p1, p2 = Payment(), Payment() 213 p1 = Payment(self.payer, self.payable) 214 p2 = Payment(self.payer, self.payable) 168 215 id1, id2 = p1.payment_id, p2.payment_id 169 216 assert id1 != id2 … … 171 218 def test_payment_id_format(self): 172 219 # payment ids have a special format: "PAY_<32 hex digits>" 173 id1 = Payment( ).payment_id220 id1 = Payment(self.payer, self.payable).payment_id 174 221 assert isinstance(id1, basestring) 175 222 assert re.match('PAY_[0-9a-f]{32}', id1) … … 177 224 def test_initial_state_is_unpaid(self): 178 225 # the initial state of payments is <unpaid> 179 p1 = Payment( )226 p1 = Payment(self.payer, self.payable) 180 227 assert p1.state == STATE_UNPAID 181 228 182 229 def test_approve(self): 183 230 # we can approve payments 184 p1 = Payment( )231 p1 = Payment(self.payer, self.payable) 185 232 p1.approve() 186 233 assert p1.state == STATE_PAID … … 190 237 def test_approve_datetime_given(self): 191 238 # we can give a datetime 192 p1 = Payment( )239 p1 = Payment(self.payer, self.payable) 193 240 some_datetime = datetime.datetime(2014, 1, 1, 0, 0, 0) 194 241 p1.approve(payment_date=some_datetime) … … 198 245 # if we do not give a datetime, current one will be used 199 246 current = datetime.datetime.utcnow() 200 p1 = Payment( )247 p1 = Payment(self.payer, self.payable) 201 248 p1.approve() 202 249 assert p1.payment_date >= current … … 204 251 def test_mark_failed(self): 205 252 # we can mark payments as failed 206 p1 = Payment( )253 p1 = Payment(self.payer, self.payable) 207 254 p1.mark_failed() 208 255 assert p1.state == STATE_FAILED 209 256 210 def test_add_payment_item(self):211 # we can add payment items212 p1 = Payment()213 item1 = PaymentItem()214 result = p1.add_payment_item(item1)215 assert len(p1) == 1 # do not make assumptions about result content216 assert isinstance(result, basestring)217 218 def test_add_payment_item_multiple(self):219 # we can add several items220 p1 = Payment()221 item1 = PaymentItem()222 item2 = PaymentItem()223 result1 = p1.add_payment_item(item1)224 result2 = p1.add_payment_item(item2)225 assert len(p1) == 2 # do not make assumptions about result content226 assert isinstance(result1, basestring)227 assert isinstance(result2, basestring)228 229 257 def test_amount(self): 230 258 # the amount of a payment is the sum of amounts of its items 231 p1 = Payment() 232 item1 = PaymentItem() 233 item2 = PaymentItem() 234 p1.add_payment_item(item1) 235 p1.add_payment_item(item2) 236 item1.amount = decimal.Decimal("12.25") 237 item2.amount = decimal.Decimal("0.5") 259 payable = self.payable 260 payable.payment_items[0].amount = decimal.Decimal("12.25") 261 payable.payment_items[1].amount = decimal.Decimal("0.5") 262 p1 = Payment(self.payer, self.payable) 238 263 assert p1.amount == decimal.Decimal("12.75") 239 264 240 265 def test_amount_negative(self): 241 266 # we can sum up negative numbers 242 p1 = Payment() 243 item1 = PaymentItem() 244 item2 = PaymentItem() 245 p1.add_payment_item(item1) 246 p1.add_payment_item(item2) 247 item1.amount = decimal.Decimal("2.21") 248 item2.amount = decimal.Decimal("-3.23") 267 payable = self.payable 268 payable.payment_items[0].amount = decimal.Decimal("2.21") 269 payable.payment_items[1].amount = decimal.Decimal("-3.23") 270 p1 = Payment(self.payer, payable) 249 271 assert p1.amount == decimal.Decimal("-1.02") 250 272 251 273 def test_amount_empty(self): 252 # the amount of zero items is 0.00.253 p 1 = Payment()254 assert p1.amount == decimal.Decimal("0.00")255 assert isinstance(p1.amount, decimal.Decimal)274 # the amount of zero items is None. 275 payable = FakePayable(payment_items=()) 276 p1 = Payment(self.payer, payable) 277 self.assertEqual(p1.amount, decimal.Decimal("0.00")) 256 278 257 279 -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_paypal.py
r12498 r12734 1174 1174 def test_store_credit_card_invalid(self): 1175 1175 # an exception is raised with invalid credit cards. 1176 s ite = self.create_site()1176 self.create_site() 1177 1177 service = PayPalCreditCardService() 1178 1178 credit_card = CreditCard( … … 1203 1203 # we can actually create payments 1204 1204 service = PayPalCreditCardService() 1205 s ite = self.create_site()1205 self.create_site() 1206 1206 credit_card = self.get_credit_card() 1207 1207 result = service.store_credit_card(credit_card)
Note: See TracChangeset for help on using the changeset viewer.