localisation++
[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 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
 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 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 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')
 
 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.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.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) :
     
     @property
     def amountWithFees(self) :
-        return self.price + self.shippingFees
+        coeff = (100 - self.discount) / 100.
+        return self.price * coeff + self.shippingFees
     
     
     security.declareProtected(ModifyPortalContent, 'editBilling')
     
     
     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')
         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 :
         
         items = []
         for item in cart :
@@ -248,6 +254,9 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
                 counters.confirm(reference, quantity)
                 
         self.items = tuple(items)
                 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, '')
 
         member = mtool.getAuthenticatedMember()
         mg = lambda name : member.getProperty(name, '')
@@ -312,12 +321,10 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
         portal = utool.getPortalObject()
         member = mtool.getAuthenticatedMember()
         
         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(),
                    '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'),
                    '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.
                    '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'],
                    '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'],
                    }
         
                    '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_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)
         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)
         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)
     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
     
         self._paypalLog.append(response)
         return response
     
@@ -376,7 +382,7 @@ class PrintOrder(PortalContent, DefaultDublinCoreImpl) :
                                                    PAYMENTREQUEST_0_CURRENCYCODE='EUR',
                                                    TOKEN=token,
                                                    PAYERID=payerid)
                                                    PAYMENTREQUEST_0_CURRENCYCODE='EUR',
                                                    TOKEN=token,
                                                    PAYERID=payerid)
-        response = Registration.recordifyPPResp(response)
+        response = PrintOrder.recordifyPPResp(response)
         self._paypalLog.append(response)
         return 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.
     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'] :
             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'
                     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
                                      , 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(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,
     def getCustomerSummary(self) :
         ' '
         return {'quantity':self.quantity,