Changeset 12740 for main/waeup.ikoba/branches/uli-payments
- Timestamp:
- 12 Mar 2015, 05:23:47 (10 years ago)
- Location:
- main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba
- Files:
-
- 1 added
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/browser.py
r12735 r12740 1532 1532 payment, view_name = service.next_step(payment.payment_id) 1533 1533 url = self.url(payment, view_name) 1534 IWorkflowInfo(self.context).fireTransition('await')1535 1534 self.redirect(url) 1536 1535 return -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/contracts.py
r12734 r12740 20 20 """ 21 21 import grok 22 from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState 22 23 from zope.catalog.interfaces import ICatalog 23 24 from zope.component import getUtility, queryUtility … … 25 26 from zope.interface import implementedBy 26 27 from zope.schema import getFields 27 from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState28 28 from waeup.ikoba.interfaces import MessageFactory as _ 29 29 from waeup.ikoba.interfaces import ( … … 33 33 IContractSelectProduct, ICustomersUtils, ISampleContract, 34 34 ISampleContractProcess, ISampleContractEdit, ISampleContractOfficialUse) 35 from waeup.ikoba.payments.interfaces import IPayer, IPayableFinder, IPayable 36 from waeup.ikoba.payments.payment import PaymentItem 35 from waeup.ikoba.payments.interfaces import ( 36 IPayer, IPayableFinder, IPayable, IPaymentWaitingForGatewayEvent, 37 STATE_PAID, STATE_FAILED, IPaymentFinishedEvent 38 ) 39 from waeup.ikoba.payments.payment import ( 40 PaymentItem, find_payable_from_payable_id, 41 ) 37 42 from waeup.ikoba.utils.helpers import attrs_to_fields 38 43 … … 248 253 249 254 255 @grok.subscribe(IPaymentWaitingForGatewayEvent) 256 def handle_payment_waiting_for_gw(event): 257 maybe_contract = find_payable_from_payable_id( 258 event.object.payable_id) 259 if IContract.providedBy(maybe_contract): 260 IWorkflowInfo(maybe_contract).fireTransition('await') 261 262 263 @grok.subscribe(IPaymentFinishedEvent) 264 def handle_payment_finished(event): 265 payment = event.object 266 maybe_contract = find_payable_from_payable_id(payment.payable_id) 267 if not IContract.providedBy(maybe_contract): 268 return 269 if payment.state == STATE_PAID: 270 IWorkflowInfo(maybe_contract).fireTransition('confirm') 271 else: 272 IWorkflowInfo(maybe_contract).fireTransition('discard') 273 274 250 275 class ContractFinder(grok.GlobalUtility): 251 276 grok.name('contracts_finder') -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/demo_provider.py
r12736 r12740 1 import datetime 1 2 import grok 3 import random 4 import re 5 from zope.event import notify 2 6 from waeup.ikoba.interfaces import MessageFactory as _ 3 7 from waeup.ikoba.browser.layout import IkobaEditFormPage, action 4 8 from waeup.ikoba.payments.interfaces import ( 5 IPaymentGatewayService, IPayment, IPayable, IPayer, IPayee,) 9 IPaymentGatewayService, IPayment, IPayable, IPayer, IPayee, 10 STATE_AWAITING_GATEWAY_CONFIRM, STATE_PAID) 6 11 from waeup.ikoba.payments.payment import ( 7 Payment, get_payment, find_payable_from_payable_id, 8 PaymentProviderServiceBase) 12 Payment, get_payment, find_payable_from_payable_id, format_amount, 13 find_payer_from_payer_id, PaymentProviderServiceBase, 14 PaymentWaitingForGatewayEvent, PaymentFinishedEvent) 9 15 10 16 … … 16 22 'Do you really want to submit?' 17 23 ) 24 25 26 RE_CC_NUMBER = re.compile('^[0-9]{9,25}$') 27 RE_CSC = re.compile('^[0-9]{3,4}$') 18 28 19 29 … … 49 59 if payment is None: 50 60 return None, None 61 if payment.state == STATE_AWAITING_GATEWAY_CONFIRM: 62 return payment, 'demo_cc2' 51 63 return payment, 'demo_cc1' 52 64 … … 61 73 pnav = 4 62 74 63 def update(self): 64 pass 75 def validate_form(self): 76 fields = ['first_name', 'last_name', 'cc_number', 'csc', 'exp_date'] 77 cls = 'form-group' 78 if len(self.request.form): 79 cls += ' has-success' 80 result = dict([(x, cls) for x in fields]) 81 if not len(self.request.form): 82 return True, result 83 err = 'form-group has-error' 84 if not self.first_name: 85 result['first_name'] = err 86 if not self.last_name: 87 result['last_name'] = err 88 if not RE_CC_NUMBER.match(self.cc_number): 89 result['cc_number'] = err 90 if not RE_CSC.match(self.csc): 91 result['csc'] = err 92 if err in result.values(): 93 return False, result 94 return True, result 95 96 def update(self, first_name=None, last_name=None, cc_number=None, 97 month=None, year=None): 98 self.payer = IPayer(find_payer_from_payer_id(self.context.payer_id)) 99 self.payable = IPayable(find_payable_from_payable_id( 100 self.context.payable_id)) 101 form = self.request.form 102 self.first_name = form.get('first_name', self.payer.first_name) 103 self.last_name = form.get('last_name', self.payer.last_name) 104 self.cc_number = form.get('cc_number', '') 105 self.month = int(form.get('exp_month', datetime.datetime.now().month)) 106 self.year = int(form.get('exp_year', datetime.datetime.now().year)) 107 self.csc = form.get('csc', '') 108 self.amount = format_amount( 109 self.context.amount, self.context.currency) 110 self.months = ''.join([ 111 '<option%s>%s</option>' % ( 112 (x == self.month) and ' selected="selected"' or '', 113 x) for x in range(1, 13)]) 114 self.years = ''.join([ 115 '<option%s>%s</option>' % ( 116 (x == self.year + 1) and ' selected="selected"' or '', 117 x) for x in range(self.year - 1, self.year + 15)]) 118 self.ok, self.validations = self.validate_form() 65 119 66 120 @action(_('Authorize Payment'), warning=WARN_FINAL_SUBMIT, 67 121 style="primary") 68 122 def authorize(self, **data): 69 print "AUTH!" 123 if not self.ok: 124 self.flash(_("Please review (red) entries below!"), 125 type='warning') 126 return 127 # XXX: payment really started, do lots of logging 128 self.context.state = STATE_AWAITING_GATEWAY_CONFIRM 129 notify(PaymentWaitingForGatewayEvent(self.context)) 130 self.redirect(self.url(self.context, 'demo_cc2')) 131 return 70 132 71 133 @action(_('Cancel')) … … 78 140 if payed_item is not None: 79 141 # remove context/payment? 80 target = self.url(payed_item)142 target = payed_item 81 143 else: 82 144 target = grok.getSite() 83 145 self.redirect(self.url(target)) 146 147 148 class CreditCardStep2(IkobaEditFormPage): 149 grok.context(IPayment) 150 grok.name('demo_cc2') 151 # XXX: Use own permissions for payments 152 grok.require('waeup.Authenticated') 153 label = " " 154 grok.template('demo_cc_step2') 155 pnav = 4 156 157 def update(self): 158 cnt = int(self.request.form.get('cnt', '0')) 159 self.cnt = cnt + 1 160 threshold = random.choice(range(10)) 161 self.success = False 162 # HERE WOULD WE REALLY ASK FOR VERIFICATION 163 if threshold <= cnt: 164 self.success = True 165 self.flash(_("Your payment was finished successfully.")) 166 if self.request.form.get('SUBMIT', None): 167 self.renew() 168 169 def renew(self): 170 if not self.success: 171 return 172 payable_id = getattr(self.context, 'payable_id', '') 173 payed_item = find_payable_from_payable_id(payable_id) 174 self.context.state = STATE_PAID 175 notify(PaymentFinishedEvent(self.context)) 176 self.redirect(self.url(payed_item)) -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/interfaces.py
r12737 r12740 20 20 from zope import schema 21 21 from zope.component import getUtilitiesFor 22 from zope.component.interfaces import IObjectEvent 22 23 from zope.interface import Interface, Attribute 23 24 from waeup.ikoba.interfaces import ( … … 28 29 #: Possible states of payments 29 30 STATE_UNPAID = 1 30 STATE_PAID = 2 31 STATE_FAILED = 4 31 STATE_AWAITING_USER_CONFIRM = 2 32 STATE_AWAITING_GATEWAY_CONFIRM = 4 33 STATE_PAID = 64 34 STATE_FAILED = 128 32 35 33 36 payment_states = SimpleIkobaVocabulary( 34 37 (_('Not yet paid'), STATE_UNPAID), 38 (_('Waiting for user confirm'), STATE_AWAITING_USER_CONFIRM), 39 (_('Waiting for verification'), STATE_AWAITING_GATEWAY_CONFIRM), 35 40 (_('Paid'), STATE_PAID), 36 41 (_('Failed'), STATE_FAILED), 37 42 ) 43 44 45 class IPaymentWaitingForGatewayEvent(IObjectEvent): 46 """Fired when a payment starts waiting for verification. 47 """ 48 object = Attribute("""The payment waiting.""") 49 50 51 class IPaymentFinishedEvent(IObjectEvent): 52 """Fired when a payment failed or succeeded. 53 """ 54 object = Attribute("""The payment finished.""") 55 56 57 class IPaymentAborted(IObjectEvent): 58 """Fired when a payment was aborted before external transactions. 59 """ 60 object = Attribute("""The payment aborted.""") 38 61 39 62 -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/payment.py
r12734 r12740 32 32 IPayment, STATE_UNPAID, STATE_FAILED, STATE_PAID, 33 33 IPaymentGatewayService, IPayer, IPaymentItem, IPayee, 34 IPaymentGatewayServicesLister, IPayableFinder, 34 IPaymentGatewayServicesLister, IPayableFinder, IPayerFinder, 35 IPaymentWaitingForGatewayEvent, IPaymentFinishedEvent, 35 36 ) 37 38 39 def format_amount(amount, currency): 40 """Turn `amount`, `currency` into a readable string. 41 """ 42 cncy_map = {'USD': u'US$', 'EUR': u'\u20ac', 'NGN': u'\u20a6'} 43 currency = cncy_map.get(currency, currency) 44 return '%s %s' % (currency, '{:,.2f}'.format(amount)) 36 45 37 46 … … 57 66 for name, util in getUtilitiesFor(IPayableFinder): 58 67 result = util.get_payable_by_id(payable_id) 68 if result is not None: 69 return result 70 return None 71 72 73 def find_payer_from_payer_id(payer_id): 74 """Find a payer from its id. 75 76 Looks up all registered IPayerFinders and returns the first 77 positive result found. 78 """ 79 for name, util in getUtilitiesFor(IPayerFinder): 80 result = util.get_payer_by_id(payer_id) 59 81 if result is not None: 60 82 return result … … 89 111 getUtilitiesFor(IPaymentGatewayService) 90 112 ) 113 114 115 class PaymentWaitingForGatewayEvent(object): 116 grok.implements(IPaymentWaitingForGatewayEvent) 117 118 def __init__(self, obj): 119 self.object = obj 120 121 122 class PaymentFinishedEvent(object): 123 grok.implements(IPaymentFinishedEvent) 124 125 def __init__(self, obj): 126 self.object = obj 91 127 92 128 -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/templates/demo_cc_step1.pt
r12723 r12740 1 1 <form class="form-horizontal"> 2 <div class="form-group"> 2 <div class="form-group" 3 tal:attributes="class view/validations/first_name"> 3 4 <label for="firstname" class="col-sm-3 control-label"> 4 5 Cardholder's First Name: … … 6 7 <div class="col-sm-5"> 7 8 <input type="text" class="form-control" placeholder="John" 8 id="firstname" /> 9 id="firstname" name="first_name" 10 tal:attributes="value view/first_name" /> 9 11 </div> 10 12 </div> 11 13 12 <div class="form-group">14 <div tal:attributes="class view/validations/last_name" class="form-group"> 13 15 <label for="lastname" class="col-sm-3 control-label"> 14 16 Cardholder's Last Name: … … 16 18 <div class="col-sm-5"> 17 19 <input type="text" class="form-control" placeholder="Smith" 18 id="lastname" /> 20 id="lastname" name="last_name" 21 tal:attributes="value view/last_name" /> 19 22 </div> 20 23 </div> 21 24 22 <div class="form-group"> 25 <div class="form-group" 26 tal:attributes="class view/validations/cc_number"> 23 27 <label for="ccnumber" class="col-sm-3 control-label"> 24 28 Credit Card Number: 25 29 </label> 26 30 <div class="col-sm-5"> 27 <input type="text" class="form-control" id="ccnumber" /> 31 <input type="text" class="form-control" id="ccnumber" 32 name="cc_number" tal:attributes="value view/cc_number" /> 28 33 </div> 29 34 </div> 30 35 31 <div class="form-group"> 36 <div class="form-group" 37 tal:attributes="class view/validations/exp_date"> 32 38 <label for="exp_date" class="col-sm-3 control-label"> 33 39 Expiration Date: … … 35 41 36 42 <div class="col-sm-2"> 37 <select class="form-control text-right" id="exp_date_month"> 38 <option class="text-right">1</option> 43 <select class="form-control text-right" id="exp_date_month" 44 name="exp_month" tal:content="structure view/months"> 45 <option>1</option> 39 46 <option>2</option> 40 47 <option selected="selected">3</option> … … 52 59 53 60 <div class="col-sm-2"> 54 <select class="form-control" id="exp_date_month"> 55 <option>2015</option> 61 <select class="form-control" id="exp_date_month" name="exp_year" 62 tal:content="structure view/years"> 63 <option>2014</option> 64 <option selected="selected">2015</option> 56 65 <option>2016</option> 57 66 <option>2017</option> 58 67 <option>2018</option> 59 68 <option>2019</option> 60 <option>2020</option>61 <option>2021</option>62 69 </select> 63 70 </div> 64 71 </div> 65 72 66 <div class="form-group"> 73 <div class="form-group" 74 tal:attributes="class view/validations/csc"> 67 75 <label for="sec_code" class="col-sm-3 control-label"> 68 76 Security Code (CSC): … … 75 83 </label> 76 84 <div class="col-sm-3"> 77 <input type="text" class="form-control" maxlength="4" id="sec_code" /> 85 <input type="password" class="form-control" maxlength="4" id="sec_code" 86 name="csc" tal:attributes="value view/csc" /> 78 87 </div> 79 88 … … 123 132 </div> 124 133 <div class="col-sm-9 text-left"> 125 <b >$ 3,012.40 USD</b>134 <b tal:content="view/amount">$ 3,012.40 USD</b> 126 135 </div> 127 136 </div> -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_demo_provider.py
r12734 r12740 1 import unittest 1 2 from zope.component import queryUtility 2 3 from zope.component.hooks import setSite … … 11 12 from waeup.ikoba.payments.payment import Payer, Payee, Payment 12 13 from waeup.ikoba.payments.demo_provider import ( 13 DemoCreditcardPaymentService, 14 DemoCreditcardPaymentService, RE_CC_NUMBER, RE_CSC, 14 15 ) 15 16 from waeup.ikoba.payments.tests.test_payment import FakePayer, FakePayable 16 17 17 18 18 class DemoCreditcarPaymentServiceTests(FunctionalTestCase): 19 class TestDemoProviderHelpers(unittest.TestCase): 20 21 def test_re_cc_number(self): 22 # we recognize valid numbers 23 assert RE_CC_NUMBER.match('a') is None 24 assert RE_CC_NUMBER.match('12345678') is None 25 assert RE_CC_NUMBER.match('1234a5678') is None 26 assert RE_CC_NUMBER.match('132456789') is not None 27 assert RE_CC_NUMBER.match('123456789012345') is not None 28 29 def test_re_csc(self): 30 # we recognize security numbers 31 assert RE_CSC.match('12') is None 32 assert RE_CSC.match('123') is not None 33 assert RE_CSC.match('1234') is not None 34 assert RE_CSC.match('12345') is None 35 assert RE_CSC.match('12A2') is None 36 37 38 class DemoCreditcardPaymentServiceTests(FunctionalTestCase): 19 39 20 40 layer = FunctionalLayer 21 41 22 42 def setUp(self): 23 super(DemoCreditcar PaymentServiceTests, self).setUp()43 super(DemoCreditcardPaymentServiceTests, self).setUp() 24 44 self.app = Company() 25 45 self.getRootFolder()['app'] = self.app -
main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_payment.py
r12734 r12740 1 # -*- coding: utf-8 -*- 1 2 ## $Id$ 2 3 ## … … 29 30 IPayment, STATE_UNPAID, STATE_PAID, STATE_FAILED, 30 31 IPaymentGatewayService, IPaymentItem, IPaymentGatewayServicesLister, 31 IPayableFinder, IPay able, IPayer,32 IPayableFinder, IPayerFinder, IPayable, IPayer, 32 33 ) 33 34 from waeup.ikoba.app import Company 34 35 from waeup.ikoba.payments.payment import ( 35 36 Payment, get_payment_providers, PaymentItem, format_payment_item_values, 36 get_payment, find_payable_from_payable_id, 37 get_payment, find_payable_from_payable_id, find_payer_from_payer_id, 38 format_amount, 37 39 ) 38 40 from waeup.ikoba.testing import (FunctionalLayer, FunctionalTestCase) … … 120 122 ], 121 123 'USD') 124 125 def test_format_amount(self): 126 # we can make amounts readable 127 D = decimal.Decimal 128 self.assertEqual(format_amount(D("0"), 'USD'), u"US$ 0.00") 129 self.assertEqual(format_amount(D("0.1"), 'EUR'), u"€ 0.10") 130 self.assertEqual(format_amount(D("-1.2"), 'NGN'), u"₦ -1.20") 131 self.assertEqual(format_amount(D("1234.5"), 'YEN'), u"YEN 1,234.50") 122 132 123 133 … … 177 187 self.assertTrue(result3 is None) 178 188 189 def test_find_payer_from_payer_id(self): 190 # we can find payables. 191 obj1 = object() 192 obj2 = object() 193 194 class FakeFinder(object): 195 valid = {'id1': obj1, 'id3': obj2} 196 197 def get_payer_by_id(self, the_id): 198 return self.valid.get(the_id) 199 200 finder1 = FakeFinder() 201 finder1.valid = {'id1': obj1} 202 finder2 = FakeFinder() 203 finder2.valid = {'id2': obj2} 204 gsm = getGlobalSiteManager() 205 try: 206 gsm.registerUtility(finder1, provided=IPayerFinder, name='f1') 207 gsm.registerUtility(finder2, provided=IPayerFinder, name='f2') 208 result1 = find_payer_from_payer_id('id1') 209 result2 = find_payer_from_payer_id('id2') 210 result3 = find_payer_from_payer_id('id3') 211 finally: 212 gsm.unregisterUtility(finder1, IPayerFinder) 213 gsm.unregisterUtility(finder2, IPayerFinder) 214 self.assertTrue(result1 is obj1) 215 self.assertTrue(result2 is obj2) 216 self.assertTrue(result3 is None) 217 179 218 180 219 class PaymentTests(FunctionalTestCase):
Note: See TracChangeset for help on using the changeset viewer.