Repository: saleor/saleor

Issue:
PayPal payment VALIDATION_ERROR 
paypal response --> 

`{'name': 'VALIDATION_ERROR', 'details': [{'field': 'transactions.item_list.items', 'issue': "Currency amount must be non-negative number, may optionally contain exactly 2 decimal places separated by '.', optional thousands separator ',', limited to 7 digits before the decimal point and currency which is a valid ISO Currency Code"}], 'message': 'Invalid request - see details', 'information_link': 'https://developer.paypal.com/docs/api/payments/#errors', 'debug_id': 'xxx'}
`

raw request post data: -->

{'data': '{"intent": "authorize", "transactions": [{"amount": {"total": "100.00", "currency": "HKD", "details": {"subtotal": "100.00", "tax": "0.00", "shipping": "0.00"}}, "item_list": {"items": [{"name": "product name", "quantity": "1", "price": "**Money(\'100.00\', \'HKD\')**", "currency": "HKD", "sku": "fandee-01"}]}, "description": "xxxxx"}], "redirect_urls": {"return_url": "http://localhost:8000/process/xxxxx/", "cancel_url": "http://localhost:8000/process/xxxxx"}, "payer": {"payment_method": "paypal"}}', 'headers': {'Content-Type': 'application/json', 'Authorization': 'xxxx'}}

package version:

prices (1.0.0)
django-prices (1.0.0)

PayPal payment VALIDATION_ERROR 
paypal response --> 

`{'name': 'VALIDATION_ERROR', 'details': [{'field': 'transactions.item_list.items', 'issue': "Currency amount must be non-negative number, may optionally contain exactly 2 decimal places separated by '.', optional thousands separator ',', limited to 7 digits before the decimal point and currency which is a valid ISO Currency Code"}], 'message': 'Invalid request - see details', 'information_link': 'https://developer.paypal.com/docs/api/payments/#errors', 'debug_id': 'xxx'}
`

raw request post data: -->

{'data': '{"intent": "authorize", "transactions": [{"amount": {"total": "100.00", "currency": "HKD", "details": {"subtotal": "100.00", "tax": "0.00", "shipping": "0.00"}}, "item_list": {"items": [{"name": "product name", "quantity": "1", "price": "**Money(\'100.00\', \'HKD\')**", "currency": "HKD", "sku": "fandee-01"}]}, "description": "xxxxx"}], "redirect_urls": {"return_url": "http://localhost:8000/process/xxxxx/", "cancel_url": "http://localhost:8000/process/xxxxx"}, "payer": {"payment_method": "paypal"}}', 'headers': {'Content-Type': 'application/json', 'Authorization': 'xxxx'}}

package version:

prices (1.0.0)
django-prices (1.0.0)



Discussion:
Thank you for report, @yxnkzg! We've recently revamped a lot of our money handling, so this seems like wrong type being passed around.

Looks like this [line is to blame](https://github.com/mirumee/saleor/blob/master/saleor/order/models.py#L299)

`line.unit_price_gross.quantize(Decimal('0.01'))` returns `Money()` so we'll need to add `.amount` at end of it to drop it to `Decimal`. Looks like we'll also need to add tests to enforce that our payment data is build correctly.
Thank you for report, @yxnkzg! We've recently revamped a lot of our money handling, so this seems like wrong type being passed around.

Looks like this [line is to blame](https://github.com/mirumee/saleor/blob/master/saleor/order/models.py#L299)

`line.unit_price_gross.quantize(Decimal('0.01'))` returns `Money()` so we'll need to add `.amount` at end of it to drop it to `Decimal`. Looks like we'll also need to add tests to enforce that our payment data is build correctly.

Resolving patch:
diff --git a/saleor/order/models.py b/saleor/order/models.py
--- a/saleor/order/models.py
+++ b/saleor/order/models.py
@@ -296,18 +296,19 @@ def get_purchased_items(self):
             PurchasedItem(
                 name=line.product_name, sku=line.product_sku,
                 quantity=line.quantity,
-                price=line.unit_price_gross.quantize(Decimal('0.01')),
-                currency=settings.DEFAULT_CURRENCY)
+                price=line.unit_price_gross.quantize(Decimal('0.01')).amount,
+                currency=line.unit_price.currency)
             for line in self.order.get_lines()]
 
         voucher = self.order.voucher
         if voucher is not None:
-            lines.append(PurchasedItem(
-                name=self.order.discount_name,
-                sku='DISCOUNT',
-                quantity=1,
-                price=-self.order.discount_amount.amount,
-                currency=self.currency))
+            lines.append(
+                PurchasedItem(
+                    name=self.order.discount_name,
+                    sku='DISCOUNT',
+                    quantity=1,
+                    price=-self.order.discount_amount.amount,
+                    currency=self.order.discount_amount.currency))
         return lines
 
     def get_total_price(self):


Test patch:
diff --git a/tests/test_checkout_integration.py b/tests/test_checkout_integration.py
--- a/tests/test_checkout_integration.py
+++ b/tests/test_checkout_integration.py
@@ -125,6 +125,14 @@ def test_checkout_flow_authenticated_user(
         'order:checkout-success', kwargs={'token': order.token})
     assert get_redirect_location(payment_response) == order_password
 
+    # Assert that payment object was created and contains correct data
+    payment = order.payments.all()[0]
+    assert payment.total == order.total.gross.amount
+    assert payment.tax == order.total.tax.amount
+    assert payment.currency == order.total.currency
+    assert payment.delivery == order.shipping_price.gross.amount
+    assert len(payment.get_purchased_items()) == len(order.get_lines())
+
 
 def test_address_without_shipping(request_cart_with_item, client, monkeypatch):
     monkeypatch.setattr(
diff --git a/tests/test_payment.py b/tests/test_payment.py
--- a/tests/test_payment.py
+++ b/tests/test_payment.py
@@ -1,10 +1,11 @@
+from decimal import Decimal
+
 from prices import Money
 
 from saleor.order.models import Payment
 
 
 def test_get_purchased_items(order_with_lines, settings, voucher):
-
     payment = Payment.objects.create(order=order_with_lines, variant='paypal')
     discount = Money('10.0', currency=settings.DEFAULT_CURRENCY)
 
@@ -15,6 +16,8 @@ def test_get_purchased_items(order_with_lines, settings, voucher):
             payment.get_purchased_items(), order_with_lines.get_lines()):
         assert p.sku == o.product_sku
         assert p.quantity == o.quantity
+        assert p.price == o.unit_price_gross.quantize(Decimal('0.01')).amount
+        assert p.currency == o.unit_price.currency
 
     order_with_lines.discount_name = 'Test'
     order_with_lines.discount_amount = discount
@@ -30,10 +33,13 @@ def test_get_purchased_items(order_with_lines, settings, voucher):
             payment.get_purchased_items()[:-1], order_with_lines.get_lines()):
         assert p.sku == o.product_sku
         assert p.quantity == o.quantity
+        assert p.price == o.unit_price_gross.quantize(Decimal('0.01')).amount
+        assert p.currency == o.unit_price.currency
 
     discounted = payment.get_purchased_items()[-1]
 
     assert discounted.name == order_with_lines.discount_name
     assert discounted.sku == 'DISCOUNT'
     assert discounted.price == -1 * order_with_lines.discount_amount.amount
+    assert discounted.currency == order_with_lines.discount_amount.currency
     assert discounted.quantity == 1