Пример #1
0
class GenericPayerBackend(object):

    url_namespace = 'payer'
    backend_name = _('Payer')
    template = 'payer_backend/redirect.html'
    payment_methods = [
        PAYMENT_METHOD_CARD,
        PAYMENT_METHOD_BANK,
        PAYMENT_METHOD_PHONE,
        PAYMENT_METHOD_INVOICE,
    ]

    def __init__(self, shop):
        self.shop = shop

        self.api = PayerPostAPI(
            agent_id=getattr(settings, 'SHOP_PAYER_BACKEND_AGENT_ID', ''),
            key_1=getattr(settings, 'SHOP_PAYER_BACKEND_ID1', ''),
            key_2=getattr(settings, 'SHOP_PAYER_BACKEND_ID2', ''),
            payment_methods=self.payment_methods,
            test_mode=getattr(settings, 'SHOP_PAYER_BACKEND_TEST_MODE', False),
            debug_mode=getattr(settings, 'SHOP_PAYER_BACKEND_DEBUG_MODE', DEBUG_MODE_SILENT),
            hide_details=getattr(settings, 'SHOP_PAYER_BACKEND_HIDE_DETAILS', False),
        )

        for ip in getattr(settings, 'SHOP_PAYER_BACKEND_IP_WHITELIST', []):
            self.api.add_whitelist_ip(ip)

        for ip in getattr(settings, 'SHOP_PAYER_BACKEND_IP_BLACKLIST', []):
            self.api.add_blacklist_ip(ip)

    def get_url_name(self, name=None):
        if not name:
            return self.url_namespace
        return '%s-%s' % (self.url_namespace, name,)

    def get_urls(self):
        urlpatterns = patterns(
            '',
            url(r'^$', self.payer_redirect_view, name=self.get_url_name()),
            url(r'^authorize/$', self.callback_notification_view, name=self.get_url_name('authorize')),
            url(r'^settle/$', self.callback_notification_view, name=self.get_url_name('settle')),
        )
        return urlpatterns

    def get_processing_control(self, request):

        return PayerProcessingControl(
            success_redirect_url=request.build_absolute_uri(self.shop.get_finished_url()),
            authorize_notification_url=request.build_absolute_uri(reverse(self.get_url_name('authorize'))),
            settle_notification_url=request.build_absolute_uri(reverse(self.get_url_name('settle'))),
            redirect_back_to_shop_url=request.build_absolute_uri(self.shop.get_cancel_url()),
        )

    @on_method(shop_login_required)
    @on_method(order_required)
    def payer_redirect_view(self, request):
        """
        This view generates the order XML data and other key-value pairs needed,
        outputs the as hidden input elemens in a form and redirects to Payer.
        """

        order = self.shop.get_order(request)
        description = self.shop.get_order_short_name(order)
        order_id = self.shop.get_order_unique_id(order)

        user = None
        if request.user.is_authenticated():
            user = request.user

        payer_order = PayerOrder(
            order_id=order_id,
            description=description,
        )
        payer_order.set_buyer_details(buyer_details_from_user(user=user, order=order))

        for order_item in order.items.all():
            payer_order.add_order_item(payer_order_item_from_order_item(order_item))

        for extra_order_price in order.extraorderpricefield_set.all():
            payer_order.add_order_item(payer_order_item_from_extra_order_price(extra_order_price))

        for info in order.extra_info.all():
            for t in list(string_chunks(info.text, 255)):
                payer_order.add_info_line(t)

        self.api.set_processing_control(self.get_processing_control(request))
        self.api.set_order(payer_order)

        redirect_data = self.api.get_post_data()
        form = PayerRedirectForm(redirect_data=redirect_data)

        ctx_data = {
            'order': order,
            'redirect_data': redirect_data,
            'form_action': self.api.get_post_url(),
            'form': form,
        }

        if settings.DEBUG:
            ctx_data['xml_data'] = self.api.get_xml_data(pretty_print=True)

        context = RequestContext(request, ctx_data)

        return render_to_response(self.template, context)

    def is_valid_remote_addr(self, request):
        def get_remote_addr(request):
            x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
            if x_forwarded_for:
                ip = x_forwarded_for.split(',')[0]
            else:
                ip = request.META.get('REMOTE_ADDR')
            return ip

        try:
            remote_addr = get_remote_addr(request)

            return self.api.validate_callback_ip(remote_addr)
        except Exception as e:
            logging.error(u"IP address callback did not validate: %s" % unicode(e))

        return False

    def is_valid_callback(self, request):

        try:
            url = request.build_absolute_uri(request.get_full_path())
            return self.api.validate_callback_url(url)
        except Exception as e:
            logging.error(u"Callback URL did not validate: %s" % unicode(e))

        return False

    def callback_notification_view(self, request):
        valid_callback = self.is_valid_remote_addr(request) and self.is_valid_callback(request)

        if valid_callback:
            try:
                self.handle_order_notifications(request.GET)
            except Exception as e:
                logging.error(u"Error handling order notification: %s" % unicode(e))

        return HttpResponse("TRUE" if valid_callback else "FALSE", content_type="text/plain")

    def handle_order_notifications(self, data):

        order_id = data.get(PayerXMLDocument.ORDER_ID_URL_PARAMETER_NAME,
                            data.get('payer_merchant_reference_id', None))
        payment_method = data.get('payer_payment_type', 'unknown')
        transaction_id = data.get('payer_payment_id', data.get('payread_payment_id', None))
        callback_type = data.get('payer_callback_type', '').lower()
        # testmode = bool(data.get('payer_testmode', 'false') == 'true')
        # added_fee = data.get('payer_added_fee', 0)

        if order_id is not None:

            order = Order.objects.get(pk=order_id)

            if callback_type == 'auth':

                # Payment approved, update order status, empty cart
                order.status = Order.CONFIRMED
                order.save()

                try:
                    cart = Cart.objects.get(pk=order.cart_pk)
                    cart.empty()
                except Cart.DoesNotExist:
                    pass

                confirmed.send(sender=self, order=order)

            elif callback_type == 'settle':

                # Payment completed, update order status, add payment
                order.status = Order.COMPLETED

                self.shop.confirm_payment(order, self.shop.get_order_total(order), transaction_id,
                                          u"Payer %s (%s)" % (unicode(self.backend_name).lower(), payment_method,))

                completed.send(sender=self, order=order)
Пример #2
0
class TestPayerPostAPI(TestCase):
    def setUp(self):
        self.api = PayerPostAPI(
            agent_id="AGENT_ID",
            key_1="6866ef97a972ba3a2c6ff8bb2812981054770162",
            key_2="1388ac756f07b0dda2961436ba8596c7b7995e94",
        )

        self.api.set_processing_control(self.getProcessingControl())
        self.api.set_order(self.getOrder())

    def getOrder(self):
        return PayerOrder(order_id="123456",
                          buyer_details=PayerBuyerDetails(
                              first_name="John",
                              last_name="Doe",
                              address_line_1="1234 Main Street",
                              postal_code="12345",
                              city="Anywhere",
                              phone_mobile="012345678",
                              email="*****@*****.**",
                          ),
                          order_items=[
                              PayerOrderItem(
                                  description='A product',
                                  price_including_vat=123.50,
                                  vat_percentage=25,
                                  quantity=4,
                              ),
                              PayerOrderItem(
                                  description='Another product',
                                  price_including_vat=123.0,
                                  vat_percentage=12.5,
                                  quantity=2,
                              ),
                          ],
                          info_lines=[
                              "Shipping with 5 work days",
                              "Additional line of order info",
                          ])

    def getProcessingControl(self):
        return PayerProcessingControl(
            success_redirect_url="http://localhost/webshop/thankyou/",
            authorize_notification_url="http://localhost/webshop/auth/",
            settle_notification_url="http://localhost/webshop/settle/",
            redirect_back_to_shop_url="http://localhost/webshop/",
        )

    def test_settings(self):
        self.assertEqual(self.api.get_post_url(), self.api.PAYER_POST_URL)
        self.assertEqual(self.api.get_post_url(),
                         "https://secure.payer.se/PostAPI_V1/InitPayFlow")

    def test_checksums(self):
        def check_checksums(xml_data, b64_data):
            checksum = self.api.get_checksum(b64_data)
            expected_checksum = hashlib.md5(
                "6866ef97a972ba3a2c6ff8bb2812981054770162" +
                base64.b64encode(xml_data) +
                "1388ac756f07b0dda2961436ba8596c7b7995e94").hexdigest()

            self.assertEqual(checksum, expected_checksum)

        xml_data = self.api.get_xml_data()
        b64_data = self.api.get_base64_data()

        check_checksums(xml_data, b64_data)
        check_checksums("foo bar", self.api.get_base64_data("foo bar"))

    def test_callback_validation(self):
        xml = self.api.xml_document

        auth_url = xml.get_authorize_notification_url(
            order_id=self.api.order.order_id)
        settle_url = xml.get_settle_notification_url(
            order_id=self.api.order.order_id)

        ad = {
            'payer_callback_type': 'auth',
            'payer_testmode': 'false',
            'payer_payment_type': 'card',
            'payer_merchant_reference_id': self.api.order.order_id,
        }

        sd = ad
        sd.update({
            'payer_callback_type': 'settle',
            'payer_added_fee': 0,
            'payer_payment_id': 'yYkYOKFf_Z@hbJj3nS41Q2xE9dVm',
            'payread_payment_id': 'yYkYOKFf_Z@hbJj3nS41Q2xE9dVm',
        })

        # We avoid using urllib.erlencode() here as incoming URL's are known to
        # be erroneously encoded, e.g. enecoded @ characters in query string

        def add_query_params(url, params):
            qsl = ["%s=%s" % (
                k,
                v,
            ) for k, v in params.iteritems()]
            return "&".join([url] + qsl)

        auth_url = add_query_params(auth_url, ad)
        settle_url = add_query_params(settle_url, sd)

        def get_md5_sum(url):
            return hashlib.md5(
                "6866ef97a972ba3a2c6ff8bb2812981054770162" + url +
                "1388ac756f07b0dda2961436ba8596c7b7995e94").hexdigest()

        auth_url_test = "%s&md5sum=%s" % (auth_url, get_md5_sum(auth_url))
        auth_url_api = "%s&md5sum=%s" % (auth_url,
                                         self.api.get_checksum(auth_url))
        settle_url_test = "%s&md5sum=%s" % (settle_url,
                                            get_md5_sum(settle_url))
        settle_url_api = "%s&md5sum=%s" % (settle_url,
                                           self.api.get_checksum(settle_url))

        self.assertTrue(self.api.validate_callback_url(auth_url_test))
        self.assertTrue(self.api.validate_callback_url(auth_url_api))

        self.assertTrue(self.api.validate_callback_url(settle_url_test))
        self.assertTrue(self.api.validate_callback_url(settle_url_api))

        self.api.suppress_validation_checks = True
        self.assertTrue(self.api.validate_callback_url("fake url"))
        self.api.suppress_validation_checks = False

        self.assertEqual(auth_url_test, auth_url_api)
        self.assertEqual(settle_url_test, settle_url_api)

        self.assertRaises(PayerURLValidationError,
                          self.api.validate_callback_url, (self.api, auth_url))
        self.assertRaises(PayerURLValidationError,
                          self.api.validate_callback_url,
                          (self.api, settle_url))
        self.assertRaises(
            PayerURLValidationError, self.api.validate_callback_url,
            (self.api, auth_url + "&md5sum=79acb36d5a10837c377e6f3f1cf9fc9c"))
        self.assertRaises(PayerURLValidationError,
                          self.api.validate_callback_url,
                          (self.api, settle_url +
                           "&md5sum=79acb36d5a10837c377e6f3f1cf9fc9c"))

        try:
            self.api.validate_callback_url(
                settle_url + "&md5sum=79acb36d5a10837c377e6f3f1cf9fc9c")
        except PayerURLValidationError as e:
            self.assertTrue(str(e).startswith("MD5 checksums did not match."))

        # IP white/blacklists
        for ip in self.api.ip_whitelist:
            self.assertTrue(self.api.validate_callback_ip(ip))

        new_ip = "123.123.123.123"

        self.assertRaises(PayerIPNotOnWhitelistException,
                          self.api.validate_callback_ip, new_ip)

        self.api.suppress_validation_checks = True
        self.assertTrue(self.api.validate_callback_ip("fake ip"))
        self.api.suppress_validation_checks = False

        self.api.add_whitelist_ip(new_ip)
        self.assertTrue(self.api.validate_callback_ip(new_ip))

        self.api.add_blacklist_ip(new_ip)
        self.api.add_blacklist_ip("234.234.234.234")

        for ip in self.api.ip_blacklist:
            self.assertRaises(PayerIPBlacklistedException,
                              self.api.validate_callback_ip, ip)

    def test_config_errors(self):
        api = PayerPostAPI(agent_id=None, key_1=None, key_2=None)

        # Checksums (keys 1 & 2)
        self.assertRaises(PayerPostAPIError, api.get_checksum, "data")

        api.key_1 = "key1"
        self.assertRaises(PayerPostAPIError, api.get_checksum, "data")

        api.key_2 = "key2"

        raised = False
        try:
            api.get_checksum("data")
        except:
            raised = True

        try:
            api.get_post_data()
        except PayerPostAPIError as e:
            self.assertEqual(e.code, PayerPostAPIError.ERROR_MISSING_ORDER)

        api.set_order(self.getOrder())

        self.assertFalse(raised, 'Exception raised')
        self.assertIsNotNone(api.get_checksum("data"))

        # Order, Processing control, agent ID
        self.assertRaises(PayerPostAPIError, api.get_post_data)

        try:
            api.get_post_data()
        except PayerPostAPIError as e:
            self.assertEqual(
                e.code, PayerPostAPIError.ERROR_MISSING_PROCESSING_CONTROL)

            self.assertEqual(
                str(e),
                repr("Error %s: %s" %
                     (e.code, e.ERROR_MESSAGES.get(e.code, "Unknown Error"))))

        api.agent_id = "AGENT_ID"
        api.set_processing_control(self.getProcessingControl())

        raised = False
        try:
            api.get_post_data()
        except:
            raised = True
        self.assertFalse(raised, 'Exception raised')

        api.agent_id = None
        self.assertRaises(PayerPostAPIError, api._generate_xml)

    def test_xml_error(self):
        data = self.api.get_xml_data()
        assert data

        self.api.xml_document = "Fake XML document"

        self.assertRaises(PayerPostAPIError, self.api.get_xml_data)