Changeset 12740 for main


Ignore:
Timestamp:
12 Mar 2015, 05:23:47 (10 years ago)
Author:
uli
Message:

Let cc payments basically run.

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  
    15321532            payment, view_name = service.next_step(payment.payment_id)
    15331533            url = self.url(payment, view_name)
    1534             IWorkflowInfo(self.context).fireTransition('await')
    15351534            self.redirect(url)
    15361535            return
  • main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/contracts.py

    r12734 r12740  
    2020"""
    2121import grok
     22from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
    2223from zope.catalog.interfaces import ICatalog
    2324from zope.component import getUtility, queryUtility
     
    2526from zope.interface import implementedBy
    2627from zope.schema import getFields
    27 from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
    2828from waeup.ikoba.interfaces import MessageFactory as _
    2929from waeup.ikoba.interfaces import (
     
    3333    IContractSelectProduct, ICustomersUtils, ISampleContract,
    3434    ISampleContractProcess, ISampleContractEdit, ISampleContractOfficialUse)
    35 from waeup.ikoba.payments.interfaces import IPayer, IPayableFinder, IPayable
    36 from waeup.ikoba.payments.payment import PaymentItem
     35from waeup.ikoba.payments.interfaces import (
     36    IPayer, IPayableFinder, IPayable, IPaymentWaitingForGatewayEvent,
     37    STATE_PAID, STATE_FAILED, IPaymentFinishedEvent
     38    )
     39from waeup.ikoba.payments.payment import (
     40    PaymentItem, find_payable_from_payable_id,
     41    )
    3742from waeup.ikoba.utils.helpers import attrs_to_fields
    3843
     
    248253
    249254
     255@grok.subscribe(IPaymentWaitingForGatewayEvent)
     256def 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)
     264def 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
    250275class ContractFinder(grok.GlobalUtility):
    251276    grok.name('contracts_finder')
  • main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/demo_provider.py

    r12736 r12740  
     1import datetime
    12import grok
     3import random
     4import re
     5from zope.event import notify
    26from waeup.ikoba.interfaces import MessageFactory as _
    37from waeup.ikoba.browser.layout import IkobaEditFormPage, action
    48from waeup.ikoba.payments.interfaces import (
    5     IPaymentGatewayService, IPayment, IPayable, IPayer, IPayee,)
     9    IPaymentGatewayService, IPayment, IPayable, IPayer, IPayee,
     10    STATE_AWAITING_GATEWAY_CONFIRM, STATE_PAID)
    611from 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)
    915
    1016
     
    1622    'Do you really want to submit?'
    1723)
     24
     25
     26RE_CC_NUMBER = re.compile('^[0-9]{9,25}$')
     27RE_CSC = re.compile('^[0-9]{3,4}$')
    1828
    1929
     
    4959        if payment is None:
    5060            return None, None
     61        if payment.state == STATE_AWAITING_GATEWAY_CONFIRM:
     62            return payment, 'demo_cc2'
    5163        return payment, 'demo_cc1'
    5264
     
    6173    pnav = 4
    6274
    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()
    65119
    66120    @action(_('Authorize Payment'), warning=WARN_FINAL_SUBMIT,
    67121            style="primary")
    68122    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
    70132
    71133    @action(_('Cancel'))
     
    78140        if payed_item is not None:
    79141            # remove context/payment?
    80             target = self.url(payed_item)
     142            target = payed_item
    81143        else:
    82144            target = grok.getSite()
    83145        self.redirect(self.url(target))
     146
     147
     148class 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 = "&nbsp;"
     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  
    2020from zope import schema
    2121from zope.component import getUtilitiesFor
     22from zope.component.interfaces import IObjectEvent
    2223from zope.interface import Interface, Attribute
    2324from waeup.ikoba.interfaces import (
     
    2829#: Possible states of payments
    2930STATE_UNPAID = 1
    30 STATE_PAID = 2
    31 STATE_FAILED = 4
     31STATE_AWAITING_USER_CONFIRM = 2
     32STATE_AWAITING_GATEWAY_CONFIRM = 4
     33STATE_PAID = 64
     34STATE_FAILED = 128
    3235
    3336payment_states = SimpleIkobaVocabulary(
    3437    (_('Not yet paid'), STATE_UNPAID),
     38    (_('Waiting for user confirm'), STATE_AWAITING_USER_CONFIRM),
     39    (_('Waiting for verification'), STATE_AWAITING_GATEWAY_CONFIRM),
    3540    (_('Paid'), STATE_PAID),
    3641    (_('Failed'), STATE_FAILED),
    3742    )
     43
     44
     45class IPaymentWaitingForGatewayEvent(IObjectEvent):
     46    """Fired when a payment starts waiting for verification.
     47    """
     48    object = Attribute("""The payment waiting.""")
     49
     50
     51class IPaymentFinishedEvent(IObjectEvent):
     52    """Fired when a payment failed or succeeded.
     53    """
     54    object = Attribute("""The payment finished.""")
     55
     56
     57class IPaymentAborted(IObjectEvent):
     58    """Fired when a payment was aborted before external transactions.
     59    """
     60    object = Attribute("""The payment aborted.""")
    3861
    3962
  • main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/payment.py

    r12734 r12740  
    3232    IPayment, STATE_UNPAID, STATE_FAILED, STATE_PAID,
    3333    IPaymentGatewayService, IPayer, IPaymentItem, IPayee,
    34     IPaymentGatewayServicesLister, IPayableFinder,
     34    IPaymentGatewayServicesLister, IPayableFinder, IPayerFinder,
     35    IPaymentWaitingForGatewayEvent, IPaymentFinishedEvent,
    3536    )
     37
     38
     39def 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))
    3645
    3746
     
    5766    for name, util in getUtilitiesFor(IPayableFinder):
    5867        result = util.get_payable_by_id(payable_id)
     68        if result is not None:
     69            return result
     70    return None
     71
     72
     73def 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)
    5981        if result is not None:
    6082            return result
     
    89111        getUtilitiesFor(IPaymentGatewayService)
    90112    )
     113
     114
     115class PaymentWaitingForGatewayEvent(object):
     116    grok.implements(IPaymentWaitingForGatewayEvent)
     117
     118    def __init__(self, obj):
     119        self.object = obj
     120
     121
     122class PaymentFinishedEvent(object):
     123    grok.implements(IPaymentFinishedEvent)
     124
     125    def __init__(self, obj):
     126        self.object = obj
    91127
    92128
  • main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/templates/demo_cc_step1.pt

    r12723 r12740  
    11<form class="form-horizontal">
    2   <div class="form-group">
     2  <div class="form-group"
     3       tal:attributes="class view/validations/first_name">
    34    <label for="firstname" class="col-sm-3 control-label">
    45      Cardholder's First Name:
     
    67    <div class="col-sm-5">
    78      <input type="text" class="form-control" placeholder="John"
    8              id="firstname" />
     9             id="firstname" name="first_name"
     10             tal:attributes="value view/first_name" />
    911    </div>
    1012  </div>
    1113
    12   <div class="form-group">
     14  <div tal:attributes="class view/validations/last_name" class="form-group">
    1315    <label for="lastname" class="col-sm-3 control-label">
    1416      Cardholder's Last Name:
     
    1618    <div class="col-sm-5">
    1719      <input type="text" class="form-control" placeholder="Smith"
    18              id="lastname" />
     20             id="lastname" name="last_name"
     21             tal:attributes="value view/last_name" />
    1922    </div>
    2023  </div>
    2124
    22   <div class="form-group">
     25  <div class="form-group"
     26       tal:attributes="class view/validations/cc_number">
    2327    <label for="ccnumber" class="col-sm-3 control-label">
    2428      Credit Card Number:
    2529    </label>
    2630    <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" />
    2833    </div>
    2934  </div>
    3035
    31   <div class="form-group">
     36  <div class="form-group"
     37       tal:attributes="class view/validations/exp_date">
    3238    <label for="exp_date" class="col-sm-3 control-label">
    3339      Expiration Date:
     
    3541
    3642    <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>
    3946        <option>2</option>
    4047        <option selected="selected">3</option>
     
    5259
    5360    <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>
    5665        <option>2016</option>
    5766        <option>2017</option>
    5867        <option>2018</option>
    5968        <option>2019</option>
    60         <option>2020</option>
    61         <option>2021</option>
    6269      </select>
    6370    </div>
    6471  </div>
    6572
    66   <div class="form-group">
     73  <div class="form-group"
     74       tal:attributes="class view/validations/csc">
    6775    <label for="sec_code" class="col-sm-3 control-label">
    6876      Security Code (CSC):
     
    7583    </label>
    7684    <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" />
    7887    </div>
    7988
     
    123132    </div>
    124133    <div class="col-sm-9 text-left">
    125       <b>$&nbsp;3,012.40&nbsp;USD</b>
     134      <b tal:content="view/amount">$&nbsp;3,012.40&nbsp;USD</b>
    126135    </div>
    127136  </div>
  • main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_demo_provider.py

    r12734 r12740  
     1import unittest
    12from zope.component import queryUtility
    23from zope.component.hooks import setSite
     
    1112from waeup.ikoba.payments.payment import Payer, Payee, Payment
    1213from waeup.ikoba.payments.demo_provider import (
    13     DemoCreditcardPaymentService,
     14    DemoCreditcardPaymentService, RE_CC_NUMBER, RE_CSC,
    1415    )
    1516from waeup.ikoba.payments.tests.test_payment import FakePayer, FakePayable
    1617
    1718
    18 class DemoCreditcarPaymentServiceTests(FunctionalTestCase):
     19class 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
     38class DemoCreditcardPaymentServiceTests(FunctionalTestCase):
    1939
    2040    layer = FunctionalLayer
    2141
    2242    def setUp(self):
    23         super(DemoCreditcarPaymentServiceTests, self).setUp()
     43        super(DemoCreditcardPaymentServiceTests, self).setUp()
    2444        self.app = Company()
    2545        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 -*-
    12## $Id$
    23##
     
    2930    IPayment, STATE_UNPAID, STATE_PAID, STATE_FAILED,
    3031    IPaymentGatewayService, IPaymentItem, IPaymentGatewayServicesLister,
    31     IPayableFinder, IPayable, IPayer,
     32    IPayableFinder, IPayerFinder, IPayable, IPayer,
    3233    )
    3334from waeup.ikoba.app import Company
    3435from waeup.ikoba.payments.payment import (
    3536    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,
    3739    )
    3840from waeup.ikoba.testing import (FunctionalLayer, FunctionalTestCase)
     
    120122             ],
    121123            '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")
    122132
    123133
     
    177187        self.assertTrue(result3 is None)
    178188
     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
    179218
    180219class PaymentTests(FunctionalTestCase):
Note: See TracChangeset for help on using the changeset viewer.