X-Git-Url: https://scm.cri.ensmp.fr/git/photoprint.git/blobdiff_plain/ccaba0f54cbd41250107d36a49154353425da8b3..b521f0db73266a7bd6b69e6a9891046a03c482ee:/order.py diff --git a/order.py b/order.py deleted file mode 100755 index 6966ff3..0000000 --- a/order.py +++ /dev/null @@ -1,516 +0,0 @@ -# -*- coding: utf-8 -*- -####################################################################################### -# Plinn - http://plinn.org # -# Copyright (C) 2009-2013 Benoît PIN # -# # -# This program is free software; you can redistribute it and/or # -# modify it under the terms of the GNU General Public License # -# as published by the Free Software Foundation; either version 2 # -# of the License, or (at your option) any later version. # -# # -# This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program; if not, write to the Free Software # -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # -####################################################################################### -""" -Print order classes - - - -""" - -from Globals import InitializeClass, PersistentMapping, Persistent -from Acquisition import Implicit -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 -from Products.CMFCore.PortalContent import PortalContent -from Products.CMFCore.permissions import ModifyPortalContent, View, ManagePortal -from Products.CMFCore.utils import getToolByName, getUtilityByInterfaceName -from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl -from Products.Plinn.utils import getPreferredLanguages -from interfaces import IPrintOrderTemplate, IPrintOrder -from permissions import ManagePrintOrderTemplate, ManagePrintOrders -from price import Price -from xml.dom.minidom import Document -from tool import COPIES_COUNTERS -from App.config import getConfiguration -try : - from paypal.interface import PayPalInterface - paypalAvailable = True -except ImportError : - paypalAvailable = False -from logging import getLogger -console = getLogger('Products.photoprint.order') - - -def getPayPalConfig() : - zopeConf = getConfiguration() - try : - conf = zopeConf.product_config['photoprint'] - except KeyError : - EnvironmentError("No photoprint configuration found in Zope environment.") - - ppconf = {'API_ENVIRONMENT' : conf['paypal_api_environment'], - 'API_USERNAME' : conf['paypal_username'], - 'API_PASSWORD' : conf['paypal_password'], - 'API_SIGNATURE' : conf['paypal_signature']} - - return ppconf - - -class PrintOrderTemplate(SimpleItem) : - """ - predefined print order - """ - implements(IPrintOrderTemplate) - - security = ClassSecurityInfo() - - def __init__(self - , id - , title='' - , description='' - , productReference='' - , maxCopies=0 - , price=0 - , VATRate=0) : - self.id = id - self.title = title - self.description = description - self.productReference = productReference - self.maxCopies = maxCopies # 0 means unlimited - self.price = Price(price, VATRate) - - security.declareProtected(ManagePrintOrderTemplate, 'edit') - def edit( self - , title='' - , description='' - , productReference='' - , maxCopies=0 - , price=0 - , VATRate=0 ) : - self.title = title - self.description = description - self.productReference = productReference - self.maxCopies = maxCopies - self.price = Price(price, VATRate) - - security.declareProtected(ManagePrintOrderTemplate, 'formWidgetData') - def formWidgetData(self, REQUEST=None, RESPONSE=None): - """formWidgetData documentation - """ - d = Document() - d.encoding = 'utf-8' - root = d.createElement('formdata') - d.appendChild(root) - - def gua(name) : - return str(getattr(self, name, '')).decode('utf-8') - - id = d.createElement('id') - id.appendChild(d.createTextNode(self.getId())) - root.appendChild(id) - - title = d.createElement('title') - title.appendChild(d.createTextNode(gua('title'))) - root.appendChild(title) - - description = d.createElement('description') - description.appendChild(d.createTextNode(gua('description'))) - root.appendChild(description) - - productReference = d.createElement('productReference') - productReference.appendChild(d.createTextNode(gua('productReference'))) - root.appendChild(productReference) - - maxCopies = d.createElement('maxCopies') - maxCopies.appendChild(d.createTextNode(str(self.maxCopies))) - root.appendChild(maxCopies) - - price = d.createElement('price') - price.appendChild(d.createTextNode(str(self.price.taxed))) - root.appendChild(price) - - vatrate = d.createElement('VATRate') - vatrate.appendChild(d.createTextNode(str(self.price.vat))) - root.appendChild(vatrate) - - if RESPONSE is not None : - RESPONSE.setHeader('content-type', 'text/xml; charset=utf-8') - - manager = getToolByName(self, 'caching_policy_manager', None) - if manager is not None: - view_name = 'formWidgetData' - headers = manager.getHTTPCachingHeaders( - self, view_name, {} - ) - - for key, value in headers: - if key == 'ETag': - RESPONSE.setHeader(key, value, literal=1) - else: - RESPONSE.setHeader(key, value) - if headers: - RESPONSE.setHeader('X-Cache-Headers-Set-By', - 'CachingPolicyManager: %s' % - '/'.join(manager.getPhysicalPath())) - - - return d.toxml('utf-8') - - -InitializeClass(PrintOrderTemplate) -PrintOrderTemplateFactory = Factory(PrintOrderTemplate) - -class PrintOrder(PortalContent, DefaultDublinCoreImpl) : - - implements(IPrintOrder) - security = ClassSecurityInfo() - - def __init__( self, id) : - DefaultDublinCoreImpl.__init__(self) - 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._paypalLog = PersistentList() - - @property - def amountWithFees(self) : - coeff = (100 - self.discount) / 100. - return self.price * coeff + self.shippingFees - - - security.declareProtected(ModifyPortalContent, 'editBilling') - def editBilling(self - , name - , address - , city - , zipcode - , country - , phone) : - self.billing['name'] = name - self.billing['address'] = address - self.billing['city'] = city - self.billing['zipcode'] = zipcode - self.billing['country'] = country - self.billing['phone'] = phone - - security.declareProtected(ModifyPortalContent, 'editShipping') - def editShipping(self, name, address, city, zipcode, country) : - self.shipping['name'] = name - self.shipping['address'] = address - self.shipping['city'] = city - self.shipping['zipcode'] = zipcode - self.shipping['country'] = country - - security.declarePrivate('loadCart') - def loadCart(self, cart): - 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 : - photo = uidh.getObject(item['cmf_uid']) - pOptions = pptool.getPrintingOptionsContainerFor(photo) - template = getattr(pOptions, item['printing_template']) - - reference = template.productReference - quantity = item['quantity'] - uPrice = template.price - self.quantity += quantity - - d = {'cmf_uid' : item['cmf_uid'] - ,'url' : photo.absolute_url() - ,'title' : template.title - ,'description' : template.description - ,'unit_price' : Price(uPrice._taxed, uPrice._rate) - ,'quantity' : quantity - ,'productReference' : reference - } - items.append(d) - self.price += uPrice * quantity - # confirm counters - if template.maxCopies : - counters = getattr(photo, COPIES_COUNTERS) - 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, '') - billing = {'name' : member.getMemberFullName(nameBefore=0) - ,'address' : mg('billing_address') - ,'city' : mg('billing_city') - ,'zipcode' : mg('billing_zipcode') - ,'country' : mg('country') - ,'phone' : mg('phone') } - self.editBilling(**billing) - - sg = lambda name : cart._shippingInfo.get(name, '') - shipping = {'name' : sg('shipping_fullname') - ,'address' : sg('shipping_address') - ,'city' : sg('shipping_city') - ,'zipcode' : sg('shipping_zipcode') - ,'country' : sg('shipping_country')} - self.editShipping(**shipping) - - self.shippingFees = pptool.getShippingFeesFor(shippable=self) - - cart._confirmed = True - cart.pendingOrderPath = self.getPhysicalPath() - - security.declareProtected(ManagePrintOrders, 'resetCopiesCounters') - def resetCopiesCounters(self) : - pptool = getToolByName(self, 'portal_photo_print') - uidh = getToolByName(self, 'portal_uidhandler') - - for item in self.items : - photo = uidh.getObject(item['cmf_uid']) - counters = getattr(photo, COPIES_COUNTERS, None) - if counters : - counters.cancel(item['productReference'], - item['quantity']) - - - def _initPayPalInterface(self) : - config = getPayPalConfig() - config['API_AUTHENTICATION_MODE'] = '3TOKEN' - ppi = PayPalInterface(**config) - return ppi - - - @staticmethod - def recordifyPPResp(response) : - d = {} - d['zopeTime'] = DateTime() - for k, v in response.raw.iteritems() : - if len(v) == 1 : - d[k] = v[0] - else : - d[k] = v - return d - - # paypal api - security.declareProtected(ModifyPortalContent, 'ppSetExpressCheckout') - def ppSetExpressCheckout(self) : - utool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IURLTool') - mtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool') - portal_url = utool() - portal = utool.getPortalObject() - member = mtool.getAuthenticatedMember() - - 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(), - 'ALLOWNOTE' : 0, # The buyer is unable to enter a note to the merchant. - 'HDRIMG' : '%s/logo.gif' % portal_url, - 'EMAIL' : member.getProperty('email'), - 'SOLUTIONTYPE' : 'Sole', # Buyer does not need to create a PayPal account to check out. This is referred to as PayPal Account Optional. - 'LANDINGPAGE' : 'Billing', # Non-PayPal account - 'BRANDNAME' : portal.getProperty('title'), - 'GIFTMESSAGEENABLE' : 0, - 'GIFTRECEIPTENABLE' : 0, - 'BUYEREMAILOPTINENABLE' : 0, # Do not enable buyer to provide email address. - 'NOSHIPPING' : 1, # PayPal does not display shipping address fields whatsoever. - 'PAYMENTREQUEST_0_SHIPTONAME' : self.billing['name'], - 'PAYMENTREQUEST_0_SHIPTOSTREET' : self.billing['address'], - 'PAYMENTREQUEST_0_SHIPTOCITY' : self.billing['city'], - 'PAYMENTREQUEST_0_SHIPTOZIP' : self.billing['zipcode'], - 'PAYMENTREQUEST_0_SHIPTOPHONENUM' : self.billing['phone'], - } - - 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 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['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) - response['url'] = ppi.generate_express_checkout_redirect_url(response['TOKEN']) - console.info(options) - console.info(response) - return response - - security.declarePrivate('ppGetExpressCheckoutDetails') - def ppGetExpressCheckoutDetails(self, token) : - ppi = self._initPayPalInterface() - response = ppi.get_express_checkout_details(TOKEN=token) - response = PrintOrder.recordifyPPResp(response) - self._paypalLog.append(response) - return response - - security.declarePrivate('ppDoExpressCheckoutPayment') - def ppDoExpressCheckoutPayment(self, token, payerid, amt) : - ppi = self._initPayPalInterface() - response = ppi.do_express_checkout_payment(PAYMENTREQUEST_0_PAYMENTACTION='Sale', - PAYMENTREQUEST_0_AMT=amt, - PAYMENTREQUEST_0_CURRENCYCODE='EUR', - TOKEN=token, - PAYERID=payerid) - response = PrintOrder.recordifyPPResp(response) - self._paypalLog.append(response) - return response - - security.declareProtected(ModifyPortalContent, 'ppPay') - def ppPay(self, token, payerid): - # assure le paiement paypal en une passe : - # récupération des détails et validation de la transaction. - - 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'] : - return False - - if details['ACK'] == 'Success' : - response = self.ppDoExpressCheckoutPayment(token, - payerid, - details['AMT']) - if response['ACK'] == 'Success' and \ - response['PAYMENTINFO_0_ACK'] == 'Success' and \ - response['PAYMENTINFO_0_PAYMENTSTATUS'] == 'Completed' : - self.paid = (DateTime(), 'paypal') - wtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IWorkflowTool') - wtool.doActionFor( self - , 'paypal_pay' - , wf_id='order_workflow' - , comments='Paiement par PayPal') - return True - return False - else : - return True - - security.declareProtected(ModifyPortalContent, 'ppCancel') - def ppCancel(self, token) : - details = self.ppGetExpressCheckoutDetails(token) - - security.declareProtected(ManagePortal, 'getPPLog') - def getPPLog(self) : - return self._paypalLog - - def getCustomerSummary(self) : - ' ' - return {'quantity':self.quantity, - 'price':self.price} - - -InitializeClass(PrintOrder) -PrintOrderFactory = Factory(PrintOrder) - - -class CopiesCounters(Persistent, Implicit) : - - def __init__(self): - self._mapping = PersistentMapping() - - def getBrowserId(self): - sdm = self.session_data_manager - bim = sdm.getBrowserIdManager() - browserId = bim.getBrowserId(create=1) - return browserId - - def _checkBrowserId(self, browserId) : - sdm = self.session_data_manager - sd = sdm.getSessionDataByKey(browserId) - return not not sd - - def __setitem__(self, reference, count) : - if not self._mapping.has_key(reference): - self._mapping[reference] = PersistentMapping() - self._mapping[reference]['pending'] = PersistentMapping() - self._mapping[reference]['confirmed'] = 0 - - globalCount = self[reference] - delta = count - globalCount - bid = self.getBrowserId() - if not self._mapping[reference]['pending'].has_key(bid) : - self._mapping[reference]['pending'][bid] = delta - else : - self._mapping[reference]['pending'][bid] += delta - - - def __getitem__(self, reference) : - item = self._mapping[reference] - globalCount = item['confirmed'] - - for browserId, count in item['pending'].items() : - if self._checkBrowserId(browserId) : - globalCount += count - else : - del self._mapping[reference]['pending'][browserId] - - return globalCount - - def get(self, reference, default=0) : - if self._mapping.has_key(reference) : - return self[reference] - else : - return default - - def getPendingCounter(self, reference) : - bid = self.getBrowserId() - if not self._checkBrowserId(bid) : - console.warn('BrowserId not found: %s' % bid) - return 0 - - count = self._mapping[reference]['pending'].get(bid, None) - if count is None : - console.warn('No pending data found for browserId %s' % bid) - return 0 - else : - return count - - def confirm(self, reference, quantity) : - pending = self.getPendingCounter(reference) - if pending != quantity : - console.warn('Pending quantity mismatch with the confirmed value: (%d, %d)' % (pending, quantity)) - - browserId = self.getBrowserId() - if self._mapping[reference]['pending'].has_key(browserId) : - del self._mapping[reference]['pending'][browserId] - self._mapping[reference]['confirmed'] += quantity - - def cancel(self, reference, quantity) : - self._mapping[reference]['confirmed'] -= quantity - - def __str__(self): - return str(self._mapping)