Ejemplo n.º 1
0
    def setUp(self):
        """ Common setup. """

        # Setup a payment interface
        self.interface = PaymentInterface()

        # Status change URL
        self.status_change_url = reverse('status_change')
Ejemplo n.º 2
0
 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)
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
 def __init__(self, context):
     self.context = context
     registry = getUtility(IRegistry)
     self.settings = registry.forInterface(IDocdataSettings)
     self.payment_interface = PaymentInterface(debug=self.docdata_debug)
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
    def __init__(self, *args, **kwargs):
        """ Make sure we have an interface available. """
        super(PaymentCluster, self).__init__(*args, **kwargs)

        self.interface = PaymentInterface(debug=DEBUG)
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
    def __init__(self, *args, **kwargs):
        """ Make sure we have an interface available. """
        super(PaymentCluster, self).__init__(*args, **kwargs)

        self.interface = PaymentInterface(debug=DEBUG)
Ejemplo n.º 9
0
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)