示例#1
0
    def get_payment_form(self, payment, locale=None, remote_addr=None):
        # Check whether we've "used" this payment already. We don't really need
        # this for MSP here, but in our current implementation, the user of the
        # payment API calls mark_submitted() (transfer_initiated=yes) on the
        # redirecting/form page. When called a second time, that will raise an
        # error.
        if payment.transfer_initiated:
            raise PaymentAlreadyUsed()  # user clicked back?

        # (1) Start transaction by messaging MultiSafepay directly.
        result = self.start_transaction(payment,
                                        locale=locale,
                                        remote_addr=remote_addr)
        # result = '''<?xml version="1.0" encoding="UTF-8"?>
        # <redirecttransaction result="ok">
        #     <transaction>
        #         <id>4039</id>
        #         <payment_url>https://pay.multisafepay.com/pay/?transaction=12345&amp;lang=nl_NL</payment_url>
        #     </transaction>
        # </redirecttransaction>'''
        # OR
        # result = '''<?xml version="1.0" encoding="UTF-8"?>
        # <redirecttransaction result="error">
        #     <error>
        #         <code>1006</code>
        #         <description>Invalid transaction ID</description>
        #     </error>
        #     <transaction><id>4326</id></transaction>
        # </redirecttransaction>'''

        # (2) Fetch URL from results.
        try:
            dom = string2dom(result)

            # FIXME: ugly code
            error = dom.getElementsByTagName('error')
            if error:
                code = error[0].getElementsByTagName('code')[0]
                code = u''.join(i.wholeText for i in code.childNodes)
                desc = error[0].getElementsByTagName('description')[0]
                desc = u''.join(i.wholeText for i in desc.childNodes)
                if code == '1006':  # Invalid transaction ID
                    # User clicked back and a used payment was attempted
                    # again, or it could simply be that the credentials are
                    # bad.
                    log('Credentials bad or payment already used', 'msp',
                        'err')
                    raise PaymentAlreadyUsed()  # user clicked back somehow?

            payment_url_node = dom.getElementsByTagName('payment_url')[0]
            payment_url = u''.join(i.wholeText
                                   for i in payment_url_node.childNodes)
        except PaymentAlreadyUsed:
            raise
        except:
            mail_admins('Error when parsing result', result)  # XXX/TEMP
            raise

        # (3) Send user to URL.
        return self.create_html_form_from_url(payment_url, 'msp_form')
示例#2
0
    def get_redirect_url(self, payment_id):
        try:
            payment = Payment.objects.get(id=payment_id)
        except Payment.DoesNotExist as e:
            pass
        else:
            # We get transaction_id in the URL.. check it.. because we
            # can.
            transaction_id = (self.request.GET.get('transaction_id').encode(
                'ascii', 'replace'))
            if str(payment.unique_key.rsplit('-', 1)[0]) == transaction_id:
                # If all is well, the transaction_report url has already
                # been called. That means there's either payment success
                # or payment failure. This isn't 100% proof though.
                # If status is still submitted/unknown, we must be prepared
                # to tell the user that that is the case.
                if payment.is_success is None:
                    next_url = payment.get_url('toosoon')
                elif payment.is_success:
                    next_url = payment.get_url('success')
                else:
                    next_url = payment.get_url('abort')

                return next_url
            else:
                e = ValueError('Mismatch of transaction_id in GET')

        mail_admins(
            u'Check failed at mollie/ideal transaction_return',
            u'Exception: %s (%r)\n\nGet: %r\n\nPost: %r\n\nMeta: %r' %
            (e, e, self.request.GET, self.request.POST, self.request.META))
        return '/?fail'
示例#3
0
    def get_redirect_url(self, payment_id):
        # Check that the user is logged in and that the payment belongs to
        # said user. If we skip this we can get user-trailing-bots hitting
        # this URL without a query_string, resulting in an assert-fail below.
        user = self.request.user
        if user.is_anonymous():
            raise Http404()  # not logged in
        try:
            payment = Payment.objects.get(id=payment_id)
        except Payment.DoesNotExist:
            raise Http404()  # does not exist
        if payment.paying_user_id != user.id:
            raise Http404()  # belongs to different user

        get = self.request.GET
        paypal = get_instance()
        try:
            paypal.process_aborted(payment, get['token'])
        except Exception as e:
            pass
        else:
            # Return to a abort url where we can wipe our tears
            return payment.get_url('abort')

        mail_admins(
            u'Check failed at paypal/paypal transaction_aborted',
            u'Exception: %s (%r)\n\nGet: %r\n\nPost: %r\n\nMeta: %r' %
            (e, e, self.request.GET, self.request.POST, self.request.META))
        return '/?fail'
示例#4
0
    def post(self, request, payment_id):
        self.check_remote_addr(request)

        log(repr(request.POST), 'targetpay', 'report')

        content_type = 'text/plain; charset=UTF-8'
        try:
            payment = Payment.objects.get(id=payment_id)
            if payment.unique_key:
                trxid = payment.unique_key.split('-', 1)[1]
                if request.POST.get('trxid') != trxid:
                    raise Payment.DoesNotExist('bad trxid?')
            elif request.POST.get('status') == 'Expired':
                # Since aug2018, TGP started sending Expired notices for
                # 3-hour old transactions that weren't picked up.
                # Mark transaction as failed, and answer with OK.
                payment.mark_aborted()
                return HttpResponse('OK', content_type=content_type)

        except Payment.DoesNotExist as e:
            mail_admins('Check failed at TGP TransactionReport',
                        (u'Exception: %s (%r)\n\nGet: %r\n\nPost: %r\n\n'
                         u'Meta: %r' %
                         (e, e, request.GET, request.POST, request.META)))
            response = HttpResponse('NAK', content_type=content_type)
            response.status_code = 500
            return response

        provider_sub = payment.unique_key.split('-', 1)[0]
        targetpay = get_instance(provider_sub)
        try:
            targetpay.request_status(payment, request)
        except Exception as e:
            if isinstance(e, AtomicUpdateDupe):
                # "duplicate status"
                status, reply = 200, 'OK'
            else:
                status, reply = 500, 'NAK'

            payinfo = {
                'id': payment.id,
                'created': payment.created,
                'is_success': payment.is_success,
                'blob': payment.blob,
            }
            mail_admins(
                (u'Replying with %s to TGP report [%s, %s, %s] '
                 u'(might indicate a problem)' %
                 (reply, payment.id, payment.is_success, payment.created)),
                (u'Exception: %s (%s)\n\nGet: %r\n\nPost: %r\n\n'
                 u'Traceback: %s\n\nMeta: %r\n\nPayment: %r' %
                 (e, e, request.GET, request.POST, traceback.format_exc(),
                  request.META, payinfo)))
            response = HttpResponse(reply, content_type=content_type)
            response.status_code = status
            return response

        return HttpResponse('OK', content_type=content_type)
示例#5
0
    def _do_request(self, template, extra_kwargs):
        kwargs = {
            'ua': self.ua,
            'account': self.account,
            'site_id': self.site_id,
            'site_secure_code': self.site_secure_code,
        }
        kwargs.update(extra_kwargs)
        body = template % kwargs

        headers = {
            'Content-Type': 'text/xml; charset=UTF-8',
            'Accept': '*/*',
            'User-Agent': self.ua,
        }

        # Apparently we get SSL handshake timeouts after 1m52. That's too
        # long. Try and reduce that by adding a defaulttimeout. Only if that
        # works can be start adding a retry.
        # NOTE: http://hg.python.org/cpython/rev/3d9a86b8cc98/
        # NOTE: http://hg.python.org/cpython/rev/ce4916ca06dd/
        socket.setdefaulttimeout(20)

        timeout_seconds = 5
        request = urllib2.Request(self.api_url,
                                  data=body.encode('utf-8'),
                                  headers=headers)

        log(body, 'msp', 'out')
        try:
            response = urllib2.urlopen(request, timeout=timeout_seconds)
        except urllib2.HTTPError as e:
            # TEMP: this could use some tweaking
            contents = e.read()
            mail_admins(
                'Incoming error to this XML', body + '\n\n--\n' + contents +
                '\n\nURL: ' + self.api_url + '\n')
            log('Got error %s with response: %s' % (e.code, contents), 'msp',
                'error')
            raise
        except Exception as e:
            # TEMP: this could use some tweaking
            mail_admins('Incoming error to this XML',
                        body + '\n\nURL: ' + self.api_url + '\n')
            log('Got error: %s' % (e, ), 'msp', 'error')
            raise
        else:
            data = response.read()
            log(data, 'msp', 'in')

        return data
示例#6
0
    def get_redirect_url(self, payment_id, transaction_key):
        try:
            payment = Payment.objects.get(id=payment_id)
        except Payment.DoesNotExist as e:
            pass
        else:
            sofort = get_instance()
            try:
                sofort.process_aborted(payment, transaction_key)
            except Exception as e:
                pass
            else:
                # Return to a abort url where we can wipe our tears
                return payment.get_url('abort')

        mail_admins(
            u'Check failed at sofort/ideal transaction_aborted',
            u'Exception: %s (%r)\n\nGet: %r\n\nPost: %r\n\nMeta: %r' %
            (e, e, self.request.GET, self.request.POST, self.request.META))
        return '/?fail'
示例#7
0
    def get_redirect_url(self, payment_id):
        # Check that the user is logged in and that the payment belongs to
        # said user. If we skip this we can get user-trailing-bots hitting
        # this URL without a query_string, resulting in an assert-fail below.
        user = self.request.user
        if user.is_anonymous():
            raise Http404()  # not logged in
        try:
            payment = Payment.objects.get(id=payment_id)
        except Payment.DoesNotExist:
            raise Http404()  # does not exist
        if payment.paying_user_id != user.id:
            raise Http404()  # belongs to different user

        get = self.request.GET
        paypal = get_instance()
        try:
            paypal.process_passed(payment, get['token'], get['PayerID'])
        except ProviderError:
            # If there is a provider error, we may want to catch
            # said error and inform the user.
            raise
        except TryDifferentPayment:
            # The user should try a different payment method. It's
            # customary to catch this (and the ProviderError) from
            # an exception handling middleware.
            raise
        except Exception as e:
            # If there is an unknown error, we won't shove a 500 in
            # the user's face but mail the admins while we send the
            # user to '/'.
            pass
        else:
            # Return to a success url where we can celebrate
            return payment.get_url('success')

        mail_admins(
            u'Check failed at paypal/paypal transaction_passed',
            u'Exception: %s (%r)\n\nGet: %r\n\nPost: %r\n\nMeta: %r' %
            (e, e, self.request.GET, self.request.POST, self.request.META))
        return '/?fail'
示例#8
0
    def get(self, request, payment_id):
        content_type = 'text/plain; charset=UTF-8'

        try:
            payment = Payment.objects.get(id=payment_id)
        except Payment.DoesNotExist as e:
            pass
        else:
            transaction_id = self.request.GET.get('transaction_id')
            mollie = get_instance()
            try:
                mollie.process_report(payment, transaction_id)
            except Exception as e:
                # XXX/FIXME: here we should:
                # (a) do the lookup
                # (b) if the lookup says 'aborted', we can rest
                # (c) if the lookup says 'paid', we have a problem
                mail_admins(
                    ('Replying with NAK to bad message from Mollie '
                     '(might indicate a problem)'),
                    (u'Exception: %s (%s)\n\nGet: %r\n\nPost: %r\n\n'
                     u'Meta: %r' % (e, e, self.request.GET, self.request.POST,
                                    self.request.META)))
                response = HttpResponse('NAK', content_type=content_type)
                response.status_code = 500
                return response
            else:
                return HttpResponse('OK', content_type=content_type)

        mail_admins(
            u'Check failed at mollie/ideal transaction_report',
            (u'Exception: %s (%s)\n\nGet: %r\n\nPost: %r\n\nMeta: %r' %
             (e, e, self.request.GET, self.request.POST, self.request.META)))
        response = HttpResponse('NAK', content_type=content_type)
        response.status_code = 500
        return response
示例#9
0
    def get(self, request):
        content_type = 'text/plain; charset=UTF-8'
        payment_id = request.GET.get('transactionid')

        try:
            payment = Payment.objects.get(id=payment_id)
        except Payment.DoesNotExist as e:
            pass
        else:
            msp = get_instance()
            try:
                msp.request_status(payment)
            except Exception as e:
                if isinstance(e, ProviderIsInactive):
                    # "provider account is inactive"; cope with MSP
                    # sending reports even though they don't accept us
                    # requesting the necessary info.
                    status, reply = 200, 'OK'
                else:
                    status, reply = 500, 'NAK'

                payinfo = {
                    'id': payment.id,
                    'created': payment.created,
                    'is_success': payment.is_success,
                    'blob': payment.blob,
                }
                mail_admins((u'Replying with %s to MSP report [%s, %s, %s] '
                             u'(might indicate a problem)' % (
                                 reply, payment.id, payment.is_success,
                                 payment.created)),
                            (u'Exception: %s (%s)\n\nGet: %r\n\nPost: %r\n\n'
                             u'Traceback: %s\n\nMeta: %r\n\nPayment: %r' % (
                                 e, e, request.GET, request.POST,
                                 traceback.format_exc(), request.META,
                                 payinfo)))
                response = HttpResponse(reply, content_type=content_type)
                response.status_code = status
                return response
            else:
                return HttpResponse('OK', content_type=content_type)

        # WTF is Apple doing? Ignore requests that have no transactionid.
        # User-Agent:
        #   Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36
        #   (KHTML, like Gecko) Chrome/31.0.1623.0 Safari/537.36"
        # Referer:
        #   (nothing)
        # Requests:
        #   17.142.152.15 - - [09/Jan/2015:01:49:47 +0100]
        #     "GET /api/msp/report HTTP/1.1" 301 0
        #   17.142.152.15 - - [09/Jan/2015:01:49:48 +0100]
        #     "GET /api/msp/report/ HTTP/1.1" 500 3
        #   17.142.152.15 - - [09/Jan/2015:01:49:48 +0100]
        #     "GET /api/msp/report/ HTTP/1.1" 500 3
        #   17.142.149.128 - - [10/Jan/2015:04:28:43 +0100]
        #     "GET /api/msp/report HTTP/1.1" 301 0
        #   17.142.149.128 - - [10/Jan/2015:04:28:46 +0100]
        #     "GET /api/msp/report/ HTTP/1.1" 500 3
        #   17.142.149.128 - - [10/Jan/2015:04:28:46 +0100]
        #     "GET /api/msp/report/ HTTP/1.1" 500 3
        #   17.142.148.235 - - [10/Jan/2015:10:49:36 +0100]
        #     "GET /api/msp/report HTTP/1.1" 301 0
        #   17.142.148.235 - - [10/Jan/2015:10:49:37 +0100]
        #     "GET /api/msp/report/ HTTP/1.1" 500 3
        #   17.142.148.235 - - [10/Jan/2015:10:49:38 +0100]
        #     "GET /api/msp/report/ HTTP/1.1" 500 3
        #   17.142.151.101 - - [10/Jan/2015:18:33:14 +0100]
        #     "GET /api/msp/report HTTP/1.1" 301 0
        #   17.142.151.101 - - [10/Jan/2015:18:33:15 +0100]
        #     "GET /api/msp/report/ HTTP/1.1" 500 3
        #   17.142.151.101 - - [10/Jan/2015:18:33:15 +0100]
        #     "GET /api/msp/report/ HTTP/1.1" 500 3
        # That's in the apple range:
        #    17.0.0.0/8 (APPLE-WWNET), the hosts don't have a PTR.
        # This is supposed to be an internal URI for the MSP provider
        # only...
        if payment_id:
            mail_admins('Check failed at msp/msp transaction_report',
                        (u'Exception: %s (%r)\n\nGet: %r\n\nPost: %r\n\n'
                         u'Meta: %r' %
                         (e, e, request.GET, request.POST, request.META)))
        response = HttpResponse('NAK', content_type=content_type)
        response.status_code = 500
        return response
示例#10
0
    def process_passed(self, payment, token, payer_id):
        """
        At this point we do not have the payment, but we're allowed to
        initiate it.
        """
        # We stored the token in the unique_key. Get it from
        # payment.unique_key directly instead of using get_unique_key().
        # Otherwise we might be creating a bogus unique_key which we
        # won't be using.
        if str(payment.unique_key).rsplit('-', 1)[0] != str(token):
            raise PaymentSuspect(
                'Found token %s differing from payment %s' %
                (token, payment.id))
        if len(token) < 16:
            raise PaymentSuspect(
                'Found invalid token %s in payment %s' %
                (token, payment.id))
        if payment.is_success is not None:
            raise PaymentSuspect(
                'Got payment status report for %s which is already %s' %
                (payment.id, payment.is_success))

        # Continue and poke Paypal to make the payment.
        # We do have the option to call GetExpressCheckoutDetails first
        # to see what the user-provided parameters are. But, we don't
        # care about any of those. Just give us the money.
        params = {
            # API options
            'METHOD': 'DoExpressCheckoutPayment',
            # Mandatory payment info
            'PAYMENTREQUEST_0_AMT': '%.2f' % (payment.get_amount(),),
            'PAYMENTREQUEST_0_CURRENCYCODE': 'EUR',  # we're in the EU..
            # Mandatory verification info
            'TOKEN': token,
            'PAYERID': payer_id,
            # Optional notification URL (IPN): we may implement this
            # later, but I believe we need to have HTTPS enabled for
            # this to work on live.
            # 'PAYMENTREQUEST_0_NOTIFYURL: 'http://example.com/notify.html',
        }

        # Do request
        response = self._do_request(params)

        # DoExpressCheckoutPayment returns something like:
        #   TOKEN=EC%2d3EM7309999999999T&SUCCESSPAGEREDIRECTREQUESTED=true
        #     &TIMESTAMP=2012%2d03%2d24T23%3a08%3a10Z&CORRELATIONID=abcdef1234567
        #     &ACK=Success&VERSION=78&BUILD=2649250&INSURANCEOPTIONSELECTED=false
        #     &SHIPPINGOPTIONISDEFAULT=false
        #     &PAYMENTINFO_0_TRANSACTIONID=11U999999E777777D
        #     &PAYMENTINFO_0_RECEIPTID=1111%2d2222%2d3333%2d4444
        #     &PAYMENTINFO_0_TRANSACTIONTYPE=expresscheckout
        #     &PAYMENTINFO_0_PAYMENTTYPE=instant
        #     &PAYMENTINFO_0_ORDERTIME=2012%2d03%2d24T23%3a08%3a07Z
        #     &PAYMENTINFO_0_AMT=10%2e00&PAYMENTINFO_0_FEEAMT=0%2e69
        #     &PAYMENTINFO_0_TAXAMT=0%2e00&PAYMENTINFO_0_CURRENCYCODE=EUR
        #     &PAYMENTINFO_0_PAYMENTSTATUS=Completed
        #     &PAYMENTINFO_0_PENDINGREASON=None&PAYMENTINFO_0_REASONCODE=None
        #     &PAYMENTINFO_0_PROTECTIONELIGIBILITY=Ineligible
        #     &PAYMENTINFO_0_PROTECTIONELIGIBILITYTYPE=None
        #     &PAYMENTINFO_0_SECUREMERCHANTACCOUNTID=ABC123XYZZZZZ
        #     &PAYMENTINFO_0_ERRORCODE=0&PAYMENTINFO_0_ACK=Success
        # There is lots of nice info in there. We store all of it for
        # later viewing in the blob.
        payment.set_blob('paypal: ' + repr(response))

        # Get the actual payment status
        is_paid = (response['PAYMENTINFO_0_ACK'] == 'Success')
        if is_paid:
            # Payment went well
            payment.mark_passed()
            # This, is a bit of a question mark though. At the moment,
            # there is no way for us to get more status if the deal
            # wasn't really sealed. We add some logging and hope for
            # the best.
            if (response['PAYMENTINFO_0_PAYMENTTYPE'] != 'instant' or
                    (decimal.Decimal(response['PAYMENTINFO_0_AMT']) !=
                        payment.get_amount()) or
                    response['PAYMENTINFO_0_PAYMENTSTATUS'] != 'Completed'):
                if mail_admins:
                    mail_admins(
                        ('Paypal payment %s not completed fully?' %
                         (payment.id,)),
                        repr(response)
                    )
            # Set payment status to succeeded
            payment.mark_succeeded()
            # Signal that something has happened
            payment_updated.send(sender=payment, change='passed')

        else:
            # This, is also a bit of a question mark. This is not
            # supposed to fail... will it autocorrect itself?
            if mail_admins:
                mail_admins(
                    ('Paypal payment %s not completed fully? (2)' %
                     (payment.id,)),
                    repr(response)
                )
            # Set payment status to aborted
            payment.mark_aborted()
            # Signal that something has happened
            payment_updated.send(sender=payment, change='aborted')