Implémentation de la remise.
[photoprint.git] / order.py
index 7b87275..6966ff3 100755 (executable)
--- a/order.py
+++ b/order.py
@@ -30,6 +30,7 @@ from AccessControl import ClassSecurityInfo
 from AccessControl.requestmethod import postonly
 from zope.interface import implements
 from zope.component.factory import Factory
+from persistent.list import PersistentList
 from OFS.SimpleItem import SimpleItem
 from ZTUtils import make_query
 from DateTime import DateTime
@@ -41,12 +42,14 @@ from Products.Plinn.utils import getPreferredLanguages
 from interfaces import IPrintOrderTemplate, IPrintOrder
 from permissions import ManagePrintOrderTemplate, ManagePrintOrders
 from price import Price
-from utils import Message as _
-from utils import translate
 from xml.dom.minidom import Document
 from tool import COPIES_COUNTERS
 from App.config import getConfiguration
-from paypal.interface import PayPalInterface
+try :
+    from paypal.interface import PayPalInterface
+    paypalAvailable = True
+except ImportError :
+    paypalAvailable = False
 from logging import getLogger
 console = getLogger('Products.photoprint.order')
 
@@ -180,16 +183,18 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
         self.id = id
         self.items = []
         self.quantity = 0
+        self.discount = 0 # discount ratio in percent
         self.price = Price(0, 0)
         # billing and shipping addresses
         self.billing = PersistentMapping()
         self.shipping = PersistentMapping()
         self.shippingFees = Price(0,0)
-        self._paymentResponse = PersistentMapping()
+        self._paypalLog = PersistentList()
     
     @property
     def amountWithFees(self) :
-        return self.price + self.shippingFees
+        coeff = (100 - self.discount) / 100.
+        return self.price * coeff + self.shippingFees
     
     
     security.declareProtected(ModifyPortalContent, 'editBilling')
@@ -220,6 +225,7 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
         pptool = getToolByName(self, 'portal_photo_print')
         uidh = getToolByName(self, 'portal_uidhandler')
         mtool = getToolByName(self, 'portal_membership')
+        utool = getToolByName(self, 'portal_url')
         
         items = []
         for item in cart :
@@ -248,6 +254,9 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
                 counters.confirm(reference, quantity)
                 
         self.items = tuple(items)
+        discount_script = getattr(utool.getPortalObject(), 'photoprint_discount', None)
+        if discount_script :
+            self.discount = discount_script(self.price, self.quantity)
 
         member = mtool.getAuthenticatedMember()
         mg = lambda name : member.getProperty(name, '')
@@ -312,12 +321,10 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
         portal = utool.getPortalObject()
         member = mtool.getAuthenticatedMember()
         
-        options = {#'PAYMENTREQUEST_0_AMT' : '99.55', # todo
-                   'PAYMENTREQUEST_0_CURRENCYCODE' : 'EUR',
+        options = {'PAYMENTREQUEST_0_CURRENCYCODE' : 'EUR',
                    'PAYMENTREQUEST_0_PAYMENTACTION' : 'Sale',
                    'RETURNURL' : '%s/photoprint_order_confirm' % self.absolute_url(),
                    'CANCELURL' : '%s/photoprint_order_cancel' % self.absolute_url(),
-                   # 'CALLBACK' : TODO
                    'ALLOWNOTE' : 0, # The buyer is unable to enter a note to the merchant.
                    'HDRIMG' : '%s/logo.gif' % portal_url,
                    'EMAIL' : member.getProperty('email'),
@@ -328,8 +335,6 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
                    'GIFTRECEIPTENABLE' : 0,
                    'BUYEREMAILOPTINENABLE' : 0, # Do not enable buyer to provide email address.
                    'NOSHIPPING' : 1, # PayPal does not display shipping address fields whatsoever.
-                   # 'PAYMENTREQUEST_0_NOTIFYURL' : TODO
-                   
                    'PAYMENTREQUEST_0_SHIPTONAME' : self.billing['name'],
                    'PAYMENTREQUEST_0_SHIPTOSTREET' : self.billing['address'],
                    'PAYMENTREQUEST_0_SHIPTOCITY' : self.billing['city'],
@@ -337,24 +342,25 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
                    'PAYMENTREQUEST_0_SHIPTOPHONENUM' : self.billing['phone'],
                    }
         
-        quantitySum = reduce(lambda a, b : a['quantity'] + b['quantity'], self.items)
-        priceSum = reduce(lambda a, b : a['unit_price'] * a['quantity'] + b['unit_price'] * b['quantity'], self.items)
-        priceValues = priceSum.getValues()
-        total = round(priceValues['taxed'], 2)
-        basePrice = round(priceValues['value'], 2)
-        tax = round(total - basePrice, 2)
+        if len(self.items) > 1 :
+            quantitySum = reduce(lambda a, b : a + b, [item['quantity'] for item in self.items])
+        else :
+            quantitySum = self.items[0]['quantity']
+        total = round(self.amountWithFees.getValues()['taxed'], 2)
         
-        options['L_PAYMENTREQUEST_0_NAME0'] = 'Commande realis photo ref. %s' % self.getId()
-        options['L_PAYMENTREQUEST_0_DESC0'] = 'Commande de %d tirages photo' % quantitySum 
+        options['L_PAYMENTREQUEST_0_NAME0'] = 'Commande photo ref. %s' % self.getId()
+        if quantitySum == 1 :
+            options['L_PAYMENTREQUEST_0_DESC0'] = "Commande d'un tirage photographique"
+        else :
+            options['L_PAYMENTREQUEST_0_DESC0'] = 'Commande de %d tirages photographiques' % quantitySum
         options['L_PAYMENTREQUEST_0_AMT0'] =  total
-        # options['L_PAYMENTREQUEST_0_TAXAMT0'] =  tax
-        # options['L_PAYMENTREQUEST_0_QTY%d' % n] = 1
+        options['PAYMENTINFO_0_SHIPPINGAMT'] = round(self.shippingFees.getValues()['taxed'], 2)
         options['PAYMENTREQUEST_0_AMT'] = total
 
         ppi = self._initPayPalInterface()
         response = ppi.set_express_checkout(**options)
         response = PrintOrder.recordifyPPResp(response)
-        self._paypalLog.append(response)
+        self._paypalLog.append(response)
         response['url'] = ppi.generate_express_checkout_redirect_url(response['TOKEN'])
         console.info(options)
         console.info(response)
@@ -364,7 +370,7 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
     def ppGetExpressCheckoutDetails(self, token) :
         ppi = self._initPayPalInterface()
         response = ppi.get_express_checkout_details(TOKEN=token)
-        response = Registration.recordifyPPResp(response)
+        response = PrintOrder.recordifyPPResp(response)
         self._paypalLog.append(response)
         return response
     
@@ -376,7 +382,7 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
                                                    PAYMENTREQUEST_0_CURRENCYCODE='EUR',
                                                    TOKEN=token,
                                                    PAYERID=payerid)
-        response = Registration.recordifyPPResp(response)
+        response = PrintOrder.recordifyPPResp(response)
         self._paypalLog.append(response)
         return response
     
@@ -384,7 +390,12 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
     def ppPay(self, token, payerid):
         # assure le paiement paypal en une passe :
         # récupération des détails et validation de la transaction.
-        if not self.paid :
+        
+        wtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IWorkflowTool')
+        wfstate = wtool.getInfoFor(self, 'review_state', 'order_workflow')
+        paid = wfstate == 'paid'
+        
+        if not paid :
             details = self.ppGetExpressCheckoutDetails(token)
 
             if payerid != details['PAYERID'] :
@@ -401,7 +412,7 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
                     wtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IWorkflowTool')
                     wtool.doActionFor( self
                                      , 'paypal_pay'
-                                     , wf_id='jma_registration_workflow'
+                                     , wf_id='order_workflow'
                                      , comments='Paiement par PayPal')
                     return True
             return False
@@ -415,153 +426,7 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
     security.declareProtected(ManagePortal, 'getPPLog')
     def getPPLog(self) :
         return self._paypalLog
-
-
-    
-    # security.declareProtected(View, 'getPaymentRequest')
-    # def getPaymentRequest(self) :
-    #   config = _getCyberplusConfig()
-    #   requester = CyberplusRequester(config)
-    #   hereurl = self.absolute_url()
-    #   amount = self.price + self.shippingFees
-    #   amount = amount.getValues()['taxed']
-    #   amount = amount * 100
-    #   amount = str(int(round(amount, 0)))
-    #   pptool = getToolByName(self, 'portal_photo_print')
-    #   transaction_id = pptool.getNextTransactionId()
-    #   
-    #   userLanguages = getPreferredLanguages(self)
-    #   for pref in userLanguages :
-    #       lang = pref.split('-')[0]
-    #       if lang in CYBERPLUS_LANGUAGES :
-    #           break
-    #   else :
-    #       lang = 'en'
-    #   
-    #   options = {  'amount': amount
-    #               ,'cancel_return_url'        : '%s/paymentCancelHandler' % hereurl
-    #               ,'normal_return_url'        : '%s/paymentManualResponseHandler' % hereurl
-    #               ,'automatic_response_url'   :'%s/paymentAutoResponseHandler' % hereurl
-    #               ,'transaction_id'           : transaction_id
-    #               ,'order_id'                 : self.getId()
-    #               ,'language'                 : lang
-    #              }
-    #   req = requester.generateRequest(options)
-    #   return req
-    # 
-    # def _decodeCyberplusResponse(self, form) :
-    #   config = _getCyberplusConfig()
-    #   responder = CyberplusResponder(config)
-    #   response = responder.getResponse(form)
-    #   return response
-    # 
-    # def _compareWithAutoResponse(self, manu) :
-    #   keys = manu.keys()
-    #   auto = self._paymentResponse
-    #   autoKeys = auto.keys()
-    #   if len(keys) != len(autoKeys) :
-    #       console.warn('Manual has not the same keys.\nauto: %r\nmanual: %r' % \
-    #           (sorted(autoKeys), sorted(keys)))
-    #   else :
-    #       for k, v in manu.items() :
-    #           if not auto.has_key(k) :
-    #               console.warn('%r field only found in manual response.' % k)
-    #           else :
-    #               if v != auto[k] :
-    #                   console.warn('data mismatch for %r\nauto: %r\nmanual: %r' % (k, auto[k], v))
-    # 
-    # def _checkOrderId(self, response) :
-    #   expected = self.getId()
-    #   assert expected == response['order_id'], \
-    #       "Cyberplus response transaction_id doesn't match the order object:\n" \
-    #       "expected: %s\n" \
-    #       "found: %s" % (expected, response['transaction_id'])
-    
-    # def _executeOrderWfTransition(self, response) :
-    #   if CyberplusResponder.transactionAccepted(response) :
-    #       wfaction = 'auto_accept_payment'
-    #   elif CyberplusResponder.transactionRefused(response) :
-    #       self.resetCopiesCounters()
-    #       wfaction = 'auto_refuse_payment'
-    #   elif CyberplusResponder.transactionCanceled(response) :
-    #       wfaction = 'auto_cancel_order'
-    #   else :
-    #       # transaction failed
-    #       wfaction = 'auto_transaction_failed'
-    # 
-    #   wtool = getToolByName(self, 'portal_workflow')
-    #   wf = wtool.getWorkflowById('order_workflow')
-    #   tdef = wf.transitions.get(wfaction)
-    #   wf._changeStateOf(self, tdef)
-    #   wtool._reindexWorkflowVariables(self)
-    
-    # security.declarePublic('paymentAutoResponseHandler')
-    # @postonly
-    # def paymentAutoResponseHandler(self, REQUEST) :
-    #   """\
-    #   Handle cyberplus payment auto response.
-    #   """
-    #   response = self._decodeCyberplusResponse(REQUEST.form)
-    #   self._checkOrderId(response)
-    #   self._paymentResponse.update(response)
-    #   self._executeOrderWfTransition(response)
-    # 
-    # @postonly
-    # def paymentManualResponseHandler(self, REQUEST) :
-    #   """\
-    #   Handle cyberplus payment manual response.
-    #   """
-    #   response = self._decodeCyberplusResponse(REQUEST.form)
-    #   self._checkOrderId(response)
-    #   
-    #   autoResponse = self._paymentResponse
-    #   if not autoResponse :
-    #       console.warn('Manual response handled before auto response at %s' % '/'.join(self.getPhysicalPath()))
-    #       self._paymentResponse.update(response)
-    #       self._executeOrderWfTransition(response)
-    #   else :
-    #       self._compareWithAutoResponse(response)
-    #       
-    #   url = '%s?%s' % (self.absolute_url(),
-    #                   make_query(portal_status_message=translate('Your payment is complete.', self).encode('utf-8'))
-    #                   )
-    #   return REQUEST.RESPONSE.redirect(url)
-    # 
-    # @postonly
-    # def paymentCancelHandler(self, REQUEST) :
-    #   """\
-    #   Handle cyberplus cancel response.
-    #   This handler can be invoqued in two cases:
-    #   - the user cancel the payment form
-    #   - the payment transaction has been refused
-    #   """
-    #   response = self._decodeCyberplusResponse(REQUEST.form)
-    #   self._checkOrderId(response)
-    #   
-    #   if self._paymentResponse :
-    #       # normaly, it happens when the transaction is refused by cyberplus.
-    #       self._compareWithAutoResponse(response)
-    # 
-    #   
-    #   if CyberplusResponder.transactionRefused(response) :
-    #       if not self._paymentResponse :
-    #           console.warn('Manual response handled before auto response at %s' % '/'.join(self.getPhysicalPath()))
-    #           self._paymentResponse.update(response)
-    #           self._executeOrderWfTransition(response)
-    #       
-    #       msg = 'Your payment has been refused.'
-    # 
-    #   else :
-    #       self._executeOrderWfTransition(response)
-    #       msg = 'Your payment has been canceled. You will be able to pay later.'
-    # 
-    #   url = '%s?%s' % (self.absolute_url(),
-    #                   make_query(portal_status_message= \
-    #                   translate(msg, self).encode('utf-8'))
-    #                   )
-    #   return REQUEST.RESPONSE.redirect(url)
         
-    
     def getCustomerSummary(self) :
         ' '
         return {'quantity':self.quantity,