Пример #1
0
 def setUp(self):
     self.ipn_record = IPNRecord(
         transaction_id=1,
         data='a=1',
     )
     self.ipn_record.save()
     self.order = Order(
         donation=100,
         status=Order.SHOPPING_CART,
     )
     self.order.save()
     self.product = Product(
         name='Test Product',
         slug='test-product',
         price=500,
         tax_deductible=50,
         min_quantity=0,
         max_quantity=10,
         current_quantity=5,
         status=Product.FOR_SALE,
     )
     self.product.save()
     self.product_in_order = ProductInOrder(
         order=self.order,
         product=self.product,
         quantity=1,
     )
     self.product_in_order.save()
Пример #2
0
class TestPayPalIPN(TestCase):
    def setUp(self):
        self.ipn_record = IPNRecord(
            transaction_id=1,
            data='a=1',
        )
        self.ipn_record.save()
        self.order = Order(
            donation=100,
            status=Order.SHOPPING_CART,
        )
        self.order.save()
        self.product = Product(
            name='Test Product',
            slug='test-product',
            price=500,
            tax_deductible=50,
            min_quantity=0,
            max_quantity=10,
            current_quantity=5,
            status=Product.FOR_SALE,
        )
        self.product.save()
        self.product_in_order = ProductInOrder(
            order=self.order,
            product=self.product,
            quantity=1,
        )
        self.product_in_order.save()

    def test_ipn_error_conditions(self):
        # Anything besides POST gets a 404
        response = self.client.get('/paypal/ipn/')
        self.assertEqual(response.status_code, 404)

        # Can't verify message with PayPal
        response = self.client.post('/paypal/ipn/', {})
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, 'Ok (unverified)')

        # Message not for our business
        response = self.client.post('/paypal/ipn/', {
            '_fake_paypal_verification': settings.SECRET_KEY,
            'business': '*****@*****.**',
        })
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, 'Ok (wrong business)')

        # No transaction ID provided
        response = self.client.post('/paypal/ipn/', {
            '_fake_paypal_verification': settings.SECRET_KEY,
            'business': settings.PAYPAL_BUSINESS,
        })
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, 'Ok (no id)')

        # Duplicate IPN message received
        response = self.client.post('/paypal/ipn/', {
            '_fake_paypal_verification': settings.SECRET_KEY,
            'business': settings.PAYPAL_BUSINESS,
            'txn_id': '1',
        })
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, 'Ok (duplicate)')

        # Status is not Completed
        response = self.client.post('/paypal/ipn/', {
            '_fake_paypal_verification': settings.SECRET_KEY,
            'business': settings.PAYPAL_BUSINESS,
            'txn_id': '2',
            'payment_status': 'Waiting',
        })
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, 'Ok (not completed)')

        # No matching order
        response = self.client.post('/paypal/ipn/', {
            '_fake_paypal_verification': settings.SECRET_KEY,
            'business': settings.PAYPAL_BUSINESS,
            'txn_id': '3',
            'payment_status': 'Completed',
        })
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, 'Ok (no order)')

        # Payment for wrong amount
        response = self.client.post('/paypal/ipn/', {
            '_fake_paypal_verification': settings.SECRET_KEY,
            'business': settings.PAYPAL_BUSINESS,
            'txn_id': '4',
            'payment_status': 'Completed',
            'invoice': self.order.invoice_id,
            'mc_gross': '1.00',
        })
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, 'Ok (wrong amount)')

    def test_ipn_success_conditions(self):
        # Order marked as paid
        response = self.client.post('/paypal/ipn/', {
            '_fake_paypal_verification': settings.SECRET_KEY,
            'business': settings.PAYPAL_BUSINESS,
            'txn_id': '5',
            'payment_status': 'Completed',
            'invoice': self.order.invoice_id,
            'mc_gross': '646.25',
        })
        order = Order.objects.get(pk=self.order.id)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, 'Ok')
        self.assertEqual(order.status, Order.PAYMENT_CONFIRMED)
        record = IPNRecord.objects.get(transaction_id='5')
        self.assertTrue(record.data)
Пример #3
0
def paypal_ipn(request):
    """Handles PayPal IPN notifications."""
    if not request.method == 'POST':
        logging.warning('IPN view hit but not POSTed to. Attackers?')
        raise Http404
    paypal_url = settings.PAYPAL_POST_URL

    # Ack and verify the message with paypal
    params = request.POST.copy()
    if params.get('_fake_paypal_verification') == settings.SECRET_KEY:
        pass
    else:
        params['cmd'] = '_notify-validate'
        req = urllib2.Request(paypal_url, urllib.urlencode(params))
        req.add_header('Content-type', 'application/x-www-form-urlencoded')
        try:
            response = urllib2.urlopen(req)
        except urllib2.URLError:
            logging.error('Unable to contact PayPal to ack the message.')
            raise Http404 # Let PayPal resend later and try again
        if response.code != 200 or response.readline() != 'VERIFIED':
            logging.warning('IPN view could not verify the message with '
                'PayPal. Original: %r' % request.POST)
            return HttpResponse('Ok (unverified)')
    params = request.POST.copy()

    # Verify that the message is for our business
    business = params.get('business')
    if business and business != settings.PAYPAL_BUSINESS:
        logging.warning('IPN received for a random business. Weird.')
        return HttpResponse('Ok (wrong business)')

    # Save the notification and ensure it's not a duplicate
    transaction_id = params.get('txn_id')
    if not transaction_id:
        logging.warning('No transaction id provided.')
        return HttpResponse('Ok (no id)')
    try:
        record = IPNRecord(transaction_id=transaction_id, data=repr(params))
        record.save()
    except IntegrityError:
        logging.warning('IPN was duplicate. Probably nothing to worry about.')
        return HttpResponse('Ok (duplicate)')

    # Verify that the status is Completed
    if params.get('payment_status') != 'Completed':
        logging.warning('Status not completed. Taking no action.')
        return HttpResponse('Ok (not completed)')

    # Verify that the amount paid matches the cart amount
    invoice_id = params.get('invoice')
    try:
        order = Order.objects.get(invoice_id=invoice_id)
    except Order.DoesNotExist:
        logging.warning('Could not find corresponding Order.')
        return HttpResponse('Ok (no order)')
    order_total = Decimal(str(order.get_as_cart()['total'])).quantize(Decimal('0.01'))
    paypal_total = Decimal(str(params['mc_gross'])).quantize(Decimal('0.01'))
    if order_total != paypal_total:
        logging.warning('PayPal payment amount (%.2f) did not match order '
            'amount (%.2f)!' % (paypal_total, order_total))
        order.status = Order.PROCESSING_ERROR
        order.save()
        return HttpResponse('Ok (wrong amount)')

    # Take action! Save the details of the order
    order.status = Order.PAYMENT_CONFIRMED

    order.user_email = params.get('payer_email', '')
    order.user_firstname = params.get('first_name', '')
    order.user_lastname = params.get('last_name', '')
    order.user_shiptoname = params.get('address_name', '')
    order.user_shiptostreet = params.get('address_street', '')
    order.user_shiptostreet2 = ''
    order.user_shiptocity = params.get('address_city', '')
    order.user_shiptostate = params.get('address_state', '')
    order.user_shiptozip = params.get('address_zip', '')
    order.user_shiptocountrycode = params.get('address_country_code', '')
    order.user_shiptophonenum = params.get('contact_phone', '')

    order.paypal_transactionid = params.get('txn_id', '')
    order.paypal_paymenttype = params.get('payment_type', '')
    try:
        order.paypal_ordertime = datetime.strptime(params.get('payment_date'), '%H:%M:%S %b %d, %Y %Z')
    except ValueError:
        order.paypal_ordertime = None
    order.paypal_amt = params.get('mc_gross')
    order.paypal_feeamt = params.get('mc_fee')
    order.paypal_paymentstatus = params.get('payment_status', '')
    order.paypal_notetext = ''

    order.paypal_details_dump = params.urlencode()
    order.save()

    # Increment the number of products sold
    for product_in_order in order.productinorder_set.all():
        product = product_in_order.product
        product.current_quantity += product_in_order.quantity
        product.save()

    # Create a confirmation email to be sent
    context = {'order': order}
    message = Message()
    message.to_email = order.user_email
    message.from_email = settings.DEFAULT_FROM_EMAIL
    message.subject = render_to_string('mail/order_confirmation_subject.txt', context)
    message.body_text = render_to_string('mail/order_confirmation.txt', context)
    message.body_html = render_to_string('mail/order_confirmation.html', context)
    message.save()

    logging.info('PayPal payment recorded successfully.')
    return HttpResponse('Ok')