def setUp(self): """ Common setup. """ # Setup a payment interface self.interface = PaymentInterface() # Status change URL self.status_change_url = reverse('status_change')
def __init__(self, *args, **kwargs): super(DocdataLegacyPaymentAdapter, self).__init__() self.payment_interface = PaymentInterface(debug=False) self.merchant_name = getattr(settings, "COWRY_DOCDATA_LEGACY_MERCHANT_NAME", None) self.merchant_password = getattr(settings, "COWRY_DOCDATA_LEGACY_LIVE_MERCHANT_PASSWORD", None)
class DocdataLegacyPaymentAdapter(AbstractPaymentAdapter): # Mapping of DocData legacy statuses to Cowry statuses. status_mapping = { 'new': PaymentStatuses.new, 'started': PaymentStatuses.in_progress, 'authorized': PaymentStatuses.pending, 'authorization_requested': PaymentStatuses.pending, 'paid': PaymentStatuses.pending, 'canceled': PaymentStatuses.cancelled, 'charged-back': PaymentStatuses.cancelled, 'chargedback': PaymentStatuses.cancelled, 'confirmed_paid': PaymentStatuses.paid, 'confirmed_chargedback': PaymentStatuses.cancelled, 'closed_success': PaymentStatuses.paid, 'closed_canceled': PaymentStatuses.cancelled, 'closed_expired': PaymentStatuses.cancelled, } def __init__(self, *args, **kwargs): super(DocdataLegacyPaymentAdapter, self).__init__() self.payment_interface = PaymentInterface(debug=False) self.merchant_name = getattr(settings, "COWRY_DOCDATA_LEGACY_MERCHANT_NAME", None) self.merchant_password = getattr(settings, "COWRY_DOCDATA_LEGACY_LIVE_MERCHANT_PASSWORD", None) def update_payment_status(self, payment, status_changed_notification=False): # We can't do anything if DocData Legacy credentials aren't available. if not self.merchant_name or not self.merchant_password: logger.error("DocData Legacy credentials aren't set. Can't update payment status.") return # Don't do anything if there's no payment or payment_order_id. if not payment or not payment.payment_order_id: return if payment.payment_method_id != 'legacy': logger.warn("Ignoring attempt to update legacy status using non-legacy payment: {0}".format(payment.payment_order_id)) return # Execute status request. status_report = self.payment_interface.status_payment_cluster(merchant_name=self.merchant_name, merchant_password=self.merchant_password, payment_cluster_key=payment.payment_order_id, report_type='xml_std') # Example status replies: # # {u'cluster_amount': u'20.00', # u'cluster_currency': u'EUR', # u'last_partial_payment_method': u'ideal-ing-1procentclub_nl', # u'last_partial_payment_process': u'paid', # u'meta_amount_received': u'paid', # u'meta_charged_back': u'N', # u'meta_considered_safe': u'true', # u'payment_cluster_process': u'closed_success', # u'payout_process': u'started'} # # {u'cluster_amount': u'40.00', # u'cluster_currency': u'EUR', # u'last_partial_payment_method': u'ing-ideal-1procentclub_nl', # u'last_partial_payment_process': u'canceled', # u'meta_amount_received': u'none', # u'meta_charged_back': u'N', # u'meta_considered_safe': u'false', # u'payment_cluster_process': u'started', # u'payout_process': u'new'} if not status_report and status_changed_notification: docdata_payment_logger(payment, PaymentLogLevels.warn, "Status changed notification received but status report was empty.") return # Find or create the DocDataPayment for current report. try: ddpayment = DocDataPayment.objects.get(docdata_payment_order=payment) except DocDataPayment.DoesNotExist: # New object created when it doesn't exist. ddpayment = DocDataPayment() ddpayment.docdata_payment_order = payment # Save some information from the report. if 'last_partial_payment_method' in status_report: ddpayment.payment_method = status_report['last_partial_payment_method'] ddpayment.save() # Figure out which key to use for the status. if 'last_partial_payment_process' in status_report: status_key = 'last_partial_payment_process' else: status_key = 'payment_cluster_process' if not status_report[status_key] in self.status_mapping: # Note: We continue to process the payment status change on this error. docdata_payment_logger(payment, PaymentLogLevels.error, "Received unknown payment status from DocData: {0}".format(status_report[status_key])) # Update the DocDataPayment status. if ddpayment.status != status_report[status_key]: docdata_payment_logger(payment, PaymentLogLevels.info, "DocData Payment: {0} -> {1}".format(ddpayment.status, status_report[status_key])) ddpayment.status = status_report[status_key] ddpayment.save() old_status = payment.status new_status = self._map_status(ddpayment.status, payment, status_report) # TODO: Move this logging to AbstractPaymentAdapter when PaymentLogEntry is not abstract. if old_status != new_status: if new_status not in PaymentStatuses.values: docdata_payment_logger(payment, PaymentLogLevels.warn, "Payment {0} -> {1}".format(old_status, PaymentStatuses.unknown)) else: docdata_payment_logger(payment, PaymentLogLevels.info, "Payment {0} -> {1}".format(old_status, new_status)) self._change_status(payment, new_status) # Note: change_status calls payment.save(). # Set the payment fee when Payment status is pending or paid. if payment.status == PaymentStatuses.pending or payment.status == PaymentStatuses.paid: self.update_payment_fee(payment, payment.latest_docdata_payment.payment_method, 'COWRY_DOCDATA_LEGACY_FEES', docdata_payment_logger) def _map_status(self, status, payment=None, status_report=None): new_status = super(DocdataLegacyPaymentAdapter, self)._map_status(status) # Status mapping override. if status_report[u'meta_considered_safe'] == u'true': docdata_payment_logger(payment, PaymentLogLevels.info, "meta_considered_safe = true") new_status = PaymentStatuses.paid return new_status
def __init__(self, context): self.context = context registry = getUtility(IRegistry) self.settings = registry.forInterface(IDocdataSettings) self.payment_interface = PaymentInterface(debug=self.docdata_debug)
class DocdataPayment(object): implements(IPaymentMethod, IDocdataPayment) adapts(Interface) title = _(u'Docdata') description = _('Payment using Docdata') icon = u'++resource++pcommerce_payment_docdata_icon.png' logo = u'++resource++pcommerce_payment_docdata_logo.png' def __init__(self, context): self.context = context registry = getUtility(IRegistry) self.settings = registry.forInterface(IDocdataSettings) self.payment_interface = PaymentInterface(debug=self.docdata_debug) def __getattr__(self, name): if hasattr(self.settings, name): return getattr(self.settings, name) raise AttributeError def mailInfo(self, order, lang=None, customer=False): return _('docdata_mailinfo', default=u"Payment processed over Docdata") def verifyPayment(self, order): """ check whether the returned values from Docdata are valid to prevent fraude check with docdata what the result of the payment was.. we use the reporting 'txt_simple', which provides enough info In the docdata interface module these states are converted to a map containing 2 booleans (paid + closed) """ params = self.context.REQUEST.form # sanity check.. if int(params['form_id']) != order.orderid: return False cluster = getattr(order.paymentdata, '_cluster') status_params = {} status_params['merchant_name'] = self.docdata_merchant_name status_params['merchant_password'] = self.docdata_merchant_password status_params['payment_cluster_key'] = cluster['payment_cluster_key'] status_params['report_type'] = 'txt_simple2' result = self.payment_interface.status_payment_cluster(**status_params) # if no exception was raised, we know we have valid results here status = None if result['paid']: if result['closed']: status = 'paid_closed' else: status = 'paid_open' else: if result['closed']: status = 'unpaid_cancelled' else: status = 'unpaid_pending' assert status order.paymentdata._payment_status = status logger.info("docdata payment result: %s" % status) return result['paid'] def getLanguage(self): """ """ languages = IUserPreferredLanguages(self.context.REQUEST) langs = languages.getPreferredLanguages() if langs: language = langs[0] else: plone_props = getToolByName(self.context, 'portal_properties') language = plone_props.site_properties.default_language language = language.split('-') return language[0] def _create_cluster_request(self, order): """ return parameters which is to be used for creating a new payment cluster """ logger.info("_create_cluster_request") language = self.getLanguage() price = CurrencyAware(order.totalincl) amount = math.ceil(price.getRoundedValue(0.01)) currency = order.currency.upper() or self.currency or 'EUR' days_pay_period = 0 email = self.email or order.address.email firstname = self.firstname or order.address.firstname lastname = self.lastname or order.address.lastname address = self.address if not address: address = order.address.address1 if order.address.address2: address += " - %s" % order.address.address2 zip = self.zip or order.address.zip city = self.city or order.address.city country = self.country or order.address.country request = {} request['merchant_name'] = self.docdata_merchant_name request['merchant_password'] = self.docdata_merchant_password request['merchant_transaction_id'] = order.orderid request['profile'] = self.docdata_profile request['client_id'] = 'pcommerce docdata' request['price'] = amount request['cur_price'] = currency request['client_email'] = email request['client_firstname'] = firstname request['client_lastname'] = lastname request['client_address'] = address request['client_zip'] = zip request['client_city'] = city request['client_country'] = country request['client_language'] = language request['days_pay_period'] = days_pay_period return request def _create_url_request(self, cluster_key): """ return parameters which is to be used for creating a new URL request """ logger.info("_create_url_request") suffix_url = "/processDocdata?form_id=" update_url = self.context.absolute_url() + suffix_url request = {} request['merchant_name'] = self.docdata_merchant_name request['payment_cluster_key'] = cluster_key request['return_url_success'] = update_url request['return_url_canceled'] = update_url request['return_url_pending'] = update_url request['return_url_error'] = update_url return request def action(self, order): """""" cluster_params = self._create_cluster_request(order) cluster = self.payment_interface.new_payment_cluster( **cluster_params) setattr(order.paymentdata, '_cluster', cluster) url_params = self._create_url_request(cluster['payment_cluster_key']) url = self.payment_interface.show_payment_cluster_url(**url_params) return url
def __init__(self, *args, **kwargs): """ Make sure we have an interface available. """ super(PaymentCluster, self).__init__(*args, **kwargs) self.interface = PaymentInterface(debug=DEBUG)
class PaymentCluster(models.Model): """ Payment cluster model. """ @classmethod def get_by_transaction_id(cls, transaction_id): """ Get the payment cluster belonging to a specific transaction_id. """ assert transaction_id logger.debug('Looking up transaction with transaction id %s', transaction_id) return cls.objects.get(pk=transaction_id) def __init__(self, *args, **kwargs): """ Make sure we have an interface available. """ super(PaymentCluster, self).__init__(*args, **kwargs) self.interface = PaymentInterface(debug=DEBUG) def __unicode__(self): """ Our natural representation is the transaction id. """ return self.transaction_id def create_cluster(self, **kwargs): """ Create a new payment cluster over at Docdata and save key and id. """ data = { 'merchant_name': MERCHANT_NAME, 'merchant_password': MERCHANT_PASSWORD, 'merchant_transaction_id': self.transaction_id, 'profile': PROFILE, } data.update(kwargs) result = self.interface.new_payment_cluster(**data) self.transaction_id = data['merchant_transaction_id'] self.cluster_key = result['payment_cluster_key'] self.cluster_id = result['payment_cluster_id'] self.save() def update_status(self): """ Go out and update the payment status, and save to database. """ assert self.cluster_key data = { 'merchant_name': MERCHANT_NAME, 'merchant_password': MERCHANT_PASSWORD, 'payment_cluster_key': self.cluster_key, 'report_type': 'txt_simple2' } result = self.interface.status_payment_cluster(**data) old_paid = self.paid old_closed = self.closed self.paid = result['paid'] self.closed = result['closed'] self.save() # Status changed? Send signal! if old_paid != self.paid or old_closed != self.closed: results = payment_status_changed.send_robust(sender=self, old_paid=old_paid, old_cloder=old_closed) # Re-raise exceptions in listeners for (receiver, response) in results: if isinstance(response, Exception): raise response else: # Status update request without change? Weird! Log! logger.warning( 'Status update requested but no change detected ' 'for transaction_id \'%s\'', self.transaction_id) def payment_url(self, **kwargs): """ Return the URL to redirect to for actual payment. """ assert self.cluster_key data = { 'merchant_name': MERCHANT_NAME, 'payment_cluster_key': self.cluster_key } data.update(**kwargs) return self.interface.show_payment_cluster_url(**data) @classmethod def clear(self, days=1): """ Remove closed payment clusters modified at least `days` ago. """ from datetime import datetime, timedelta # Too bad these little statistics *might* hurt performance old_count = self.objects.all().count() oldest = datetime.now() - timedelta(days=days) self.objects.filter(closed=True, modified__lt=oldest).delete() logging.info('Deleted %d old PaymentClusters', old_count - self.objects.all().count()) transaction_id = models.CharField(max_length=35, primary_key=True) cluster_key = models.CharField(max_length=255) cluster_id = models.CharField(max_length=255) paid = models.BooleanField(default=False) closed = models.BooleanField(default=False) created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True)
class PaymentCluster(models.Model): """ Payment cluster model. """ @classmethod def get_by_transaction_id(cls, transaction_id): """ Get the payment cluster belonging to a specific transaction_id. """ assert transaction_id logger.debug('Looking up transaction with transaction id %s', transaction_id) return cls.objects.get(pk=transaction_id) def __init__(self, *args, **kwargs): """ Make sure we have an interface available. """ super(PaymentCluster, self).__init__(*args, **kwargs) self.interface = PaymentInterface(debug=DEBUG) def __unicode__(self): """ Our natural representation is the transaction id. """ return self.transaction_id def create_cluster(self, **kwargs): """ Create a new payment cluster over at Docdata and save key and id. """ data = {'merchant_name': MERCHANT_NAME, 'merchant_password': MERCHANT_PASSWORD, 'merchant_transaction_id': self.transaction_id, 'profile': PROFILE,} data.update(kwargs) result = self.interface.new_payment_cluster(**data) self.transaction_id = data['merchant_transaction_id'] self.cluster_key = result['payment_cluster_key'] self.cluster_id = result['payment_cluster_id'] self.save() def update_status(self): """ Go out and update the payment status, and save to database. """ assert self.cluster_key data = {'merchant_name': MERCHANT_NAME, 'merchant_password': MERCHANT_PASSWORD, 'payment_cluster_key': self.cluster_key, 'report_type': 'txt_simple2'} result = self.interface.status_payment_cluster(**data) old_paid = self.paid old_closed = self.closed self.paid = result['paid'] self.closed = result['closed'] self.save() # Status changed? Send signal! if old_paid != self.paid or old_closed != self.closed: results = payment_status_changed.send_robust(sender=self, old_paid=old_paid, old_cloder=old_closed) # Re-raise exceptions in listeners for (receiver, response) in results: if isinstance(response, Exception): raise response else: # Status update request without change? Weird! Log! logger.warning('Status update requested but no change detected '+ 'for transaction_id \'%s\'', self.transaction_id) def payment_url(self, **kwargs): """ Return the URL to redirect to for actual payment. """ assert self.cluster_key data = {'merchant_name': MERCHANT_NAME, 'payment_cluster_key': self.cluster_key } data.update(**kwargs) return self.interface.show_payment_cluster_url(**data) @classmethod def clear(self, days=1): """ Remove closed payment clusters modified at least `days` ago. """ from datetime import datetime, timedelta # Too bad these little statistics *might* hurt performance old_count = self.objects.all().count() oldest = datetime.now() - timedelta(days=days) self.objects.filter(closed=True, modified__lt=oldest).delete() logging.info('Deleted %d old PaymentClusters', old_count - self.objects.all().count()) transaction_id = models.CharField(max_length=35, primary_key=True) cluster_key = models.CharField(max_length=255) cluster_id = models.CharField(max_length=255) paid = models.BooleanField(default=False) closed = models.BooleanField(default=False) created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True)