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/payments
Files:
1 added
6 edited

Legend:

Unmodified
Added
Removed
  • 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.