diff --git a/src/pretix/plugins/paypal2/views.py b/src/pretix/plugins/paypal2/views.py index 7a125e4593..b1d172bccb 100644 --- a/src/pretix/plugins/paypal2/views.py +++ b/src/pretix/plugins/paypal2/views.py @@ -471,8 +471,8 @@ def webhook(request, *args, **kwargs): elif payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED): if sale['status'] == 'COMPLETED': - any_captures = False all_captures_completed = True + any_pending_review = False for purchaseunit in sale['purchase_units']: for capture in purchaseunit['payments']['captures']: try: @@ -483,9 +483,9 @@ def webhook(request, *args, **kwargs): if capture['status'] not in ('COMPLETED', 'REFUNDED', 'PARTIALLY_REFUNDED'): all_captures_completed = False - else: - any_captures = True - if any_captures and all_captures_completed: + if capture['status_details']['reason'] == "PENDING_REVIEW": + any_pending_review = True + if all_captures_completed: try: payment.info = json.dumps(sale.dict()) payment.save(update_fields=['info']) @@ -493,6 +493,9 @@ def webhook(request, *args, **kwargs): prov.log_payment_duration(payment) except Quota.QuotaExceededException: pass + if any_pending_review: + payment.state = OrderPayment.PAYMENT_STATE_PENDING + payment.save() elif sale['status'] == 'APPROVED': try: request.session['payment_paypal_oid'] = payment.info_data['id'] diff --git a/src/tests/plugins/paypal2/test_webhook.py b/src/tests/plugins/paypal2/test_webhook.py index 21eb888531..6f889dd60c 100644 --- a/src/tests/plugins/paypal2/test_webhook.py +++ b/src/tests/plugins/paypal2/test_webhook.py @@ -244,6 +244,60 @@ def get_test_refund(): } +def get_test_order_review_pending(): + return {'id': '806440346Y391300T', + 'intent': 'CAPTURE', + 'status': 'COMPLETED', + 'purchase_units': [{'reference_id': 'default', + 'amount': {'currency_code': 'EUR', 'value': '43.59'}, + 'payee': {'email_address': 'dummy-facilitator@dummy.dummy', + 'merchant_id': 'G6R2B9YXADKWW'}, + 'description': 'Order JWJGC for PayPal v2', + 'custom_id': 'Order PAYPALV2-JWJGC', + 'soft_descriptor': 'MARTINFACIL', + 'payments': {'captures': [{'id': '22A4162004478570J', + 'status': 'PENDING', + 'status_details': { + 'reason': 'PENDING_REVIEW' + }, + 'amount': {'currency_code': 'EUR', 'value': '43.59'}, + 'final_capture': True, + 'disbursement_mode': 'INSTANT', + 'seller_protection': {'status': 'ELIGIBLE', + 'dispute_categories': [ + 'ITEM_NOT_RECEIVED', + 'UNAUTHORIZED_TRANSACTION']}, + 'seller_receivable_breakdown': { + 'gross_amount': {'currency_code': 'EUR', + 'value': '43.59'}, + 'paypal_fee': {'currency_code': 'EUR', 'value': '1.18'}, + 'net_amount': {'currency_code': 'EUR', + 'value': '42.41'}}, + 'custom_id': 'Order PAYPALV2-JWJGC', + 'links': [{ + 'href': 'https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J', + 'rel': 'self', + 'method': 'GET'}, + { + 'href': 'https://api.sandbox.paypal.com/v2/payments/captures/22A4162004478570J/refund', + 'rel': 'refund', + 'method': 'POST'}, + { + 'href': 'https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T', + 'rel': 'up', + 'method': 'GET'}], + 'create_time': '2022-04-28T12:00:22Z', + 'update_time': '2022-04-28T12:00:22Z'}]}}], + 'payer': {'name': {'given_name': 'test', 'surname': 'buyer'}, + 'email_address': 'dummy@dummy.dummy', + 'payer_id': 'Q739JNKWH67HE', + 'address': {'country_code': 'DE'}}, + 'create_time': '2022-04-28T11:59:59Z', + 'update_time': '2022-04-28T12:00:22Z', + 'links': [{'href': 'https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T', + 'rel': 'self', + 'method': 'GET'}]} + class Object(): pass @@ -690,3 +744,95 @@ def test_webhook_refund2(env, client, monkeypatch): assert r.payment == order.payments.first() assert r.state == OrderRefund.REFUND_STATE_EXTERNAL assert r.source == OrderRefund.REFUND_SOURCE_EXTERNAL + + +@pytest.mark.django_db +def test_webhook_pending_payment(env, client, monkeypatch): + order = env[1] + order.status = Order.STATUS_PENDING + order.save() + with scopes_disabled(): + order.payments.update(state=OrderPayment.PAYMENT_STATE_CREATED) + + pp_order = Result(get_test_order_review_pending()) + mock_orders_get_request = MagicMock(return_value=pp_order) + monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", mock_orders_get_request) + monkeypatch.setattr("pretix.plugins.paypal2.payment.PaypalMethod.init_api", init_api) + with scopes_disabled(): + ReferencedPayPalObject.objects.create(order=order, payment=order.payments.first(), + reference="806440346Y391300T") + + assert order.payments.first().state == OrderPayment.PAYMENT_STATE_CREATED + + client.post('/_paypal/webhook/', json.dumps( + { + "id": "WH-0AH02875JL566842H-2AF03788XV8252724", + "create_time": "2022-04-28T12:00:37.077Z", + "resource_type": "capture", + "event_type": "PAYMENT.CAPTURE.PENDING", + "summary": "Payment pending for € 43.59 EUR", + "resource": { + "update_time": "2022-04-28T12:00:22Z", + "create_time": "2022-04-28T11:59:59Z", + "amount": { + "currency_code": "EUR", + "value": "43.59" + }, + "custom_id": "Order PAYPALV2-JWJGC", + "final_capture": True, + "id": "22A4162004478570J", + "links": [ + { + "href": "https://api.sandbox.paypal.com/v2/payments/captures/5M631111V9599860P", + "method": "GET", + "rel": "self" + }, + { + "href": "https://api.sandbox.paypal.com/v2/payments/captures/5M631111V9599860P/refund", + "method": "POST", + "rel": "refund" + }, + { + "href": "https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T", + "method": "GET", + "rel": "up" + } + ], + "payee": { + "email_address": "sb-ybfun52428692@business.example.com", + "merchant_id": "DLM8QKDR3CFZW" + }, + "seller_protection": { + "status": "NOT_ELIGIBLE" + }, + "status": "PENDING", + "status_details": { + "reason": "PENDING_REVIEW" + }, + "supplementary_data": { + "related_ids": { + "order_id": "9L827155WD164573M" + } + } + }, + "links": [ + { + "href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-0AH02875JL566842H-2AF03788XV8252724", + "method": "GET", + "rel": "self" + }, + { + "href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-0AH02875JL566842H-2AF03788XV8252724/resend", + "method": "POST", + "rel": "resend" + } + ], + "event_version": "1.0", + "resource_version": "2.0" + } + ), content_type='application_json') + + order = env[1] + order.refresh_from_db() + with scopes_disabled(): + assert order.payments.first().state == OrderPayment.PAYMENT_STATE_PENDING