|
29 | 29 | from flask import Flask, g, jsonify, request, has_request_context |
30 | 30 | from flask_cors import CORS |
31 | 31 |
|
32 | | -APP_VERSION = "2.3.3-nowpayments-ipnstreamfix" |
| 32 | +APP_VERSION = "2.3.1-nowpayments-ipnfix" |
33 | 33 | TRANSFER_TOPIC = "0xddf252ad00000000000000000000000000000000000000000000000000000000" |
34 | 34 | ZERO_EVM = "0x0000000000000000000000000000000000000000" |
35 | 35 | BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" |
@@ -4848,72 +4848,19 @@ def nowpayments_order_status(order_id: str): |
4848 | 4848 | }) |
4849 | 4849 |
|
4850 | 4850 |
|
4851 | | -def parse_nowpayments_ipn_payload() -> Tuple[Dict[str, Any], Dict[str, Any]]: |
| 4851 | +def parse_nowpayments_ipn_payload() -> Dict[str, Any]: |
4852 | 4852 | payload: Dict[str, Any] = {} |
4853 | | - debug: Dict[str, Any] = {} |
4854 | 4853 |
|
4855 | | - raw_bytes = b"" |
4856 | | - try: |
4857 | | - raw_bytes = request.get_data(cache=True, as_text=False, parse_form_data=False) or b"" |
4858 | | - except TypeError: |
4859 | | - raw_bytes = request.get_data(cache=True, as_text=False) or b"" |
4860 | | - except Exception: |
4861 | | - raw_bytes = b"" |
4862 | | - |
4863 | | - if not raw_bytes: |
4864 | | - try: |
4865 | | - content_length = int(request.environ.get("CONTENT_LENGTH") or 0) |
4866 | | - except Exception: |
4867 | | - content_length = 0 |
4868 | | - if content_length > 0: |
4869 | | - try: |
4870 | | - stream = request.environ.get("wsgi.input") |
4871 | | - if stream is not None: |
4872 | | - raw_bytes = stream.read(content_length) or b"" |
4873 | | - except Exception: |
4874 | | - raw_bytes = b"" |
4875 | | - |
4876 | | - raw_text = "" |
4877 | | - if raw_bytes: |
4878 | | - try: |
4879 | | - raw_text = raw_bytes.decode("utf-8-sig", errors="replace") |
4880 | | - except Exception: |
4881 | | - raw_text = str(raw_bytes) |
4882 | | - |
4883 | | - if raw_text: |
4884 | | - parsed = None |
| 4854 | + raw = request.get_data(cache=True, as_text=True) or "" |
| 4855 | + if raw: |
4885 | 4856 | try: |
4886 | | - parsed = json.loads(raw_text) |
| 4857 | + parsed = json.loads(raw) |
| 4858 | + if isinstance(parsed, dict): |
| 4859 | + payload.update(parsed) |
4887 | 4860 | except Exception: |
4888 | | - parsed = None |
4889 | | - if isinstance(parsed, str) and parsed.strip(): |
4890 | | - try: |
4891 | | - parsed = json.loads(parsed) |
4892 | | - except Exception: |
4893 | | - parsed = None |
4894 | | - if isinstance(parsed, dict): |
4895 | | - payload.update(parsed) |
4896 | | - else: |
4897 | | - try: |
4898 | | - form_like = urllib.parse.parse_qs(raw_text, keep_blank_values=True) |
4899 | | - for key, values in form_like.items(): |
4900 | | - if not values: |
4901 | | - continue |
4902 | | - field_value = values[-1] |
4903 | | - if key in {"payload", "body", "data"} and isinstance(field_value, str) and field_value.strip(): |
4904 | | - try: |
4905 | | - nested = json.loads(field_value) |
4906 | | - if isinstance(nested, dict): |
4907 | | - payload.update(nested) |
4908 | | - continue |
4909 | | - except Exception: |
4910 | | - pass |
4911 | | - if key not in payload: |
4912 | | - payload[str(key)] = field_value |
4913 | | - except Exception: |
4914 | | - pass |
| 4861 | + pass |
4915 | 4862 |
|
4916 | | - json_payload = request.get_json(silent=True, force=False) |
| 4863 | + json_payload = request.get_json(silent=True) |
4917 | 4864 | if isinstance(json_payload, dict): |
4918 | 4865 | payload.update(json_payload) |
4919 | 4866 |
|
@@ -4942,37 +4889,25 @@ def parse_nowpayments_ipn_payload() -> Tuple[Dict[str, Any], Dict[str, Any]]: |
4942 | 4889 | except Exception: |
4943 | 4890 | pass |
4944 | 4891 |
|
4945 | | - debug.update({ |
4946 | | - "content_type": str(request.content_type or ""), |
4947 | | - "mimetype": str(request.mimetype or ""), |
4948 | | - "content_length": int(request.content_length or 0), |
4949 | | - "raw_len": len(raw_bytes), |
4950 | | - "raw_preview": raw_text[:200], |
4951 | | - "payload_keys": sorted([str(k) for k in payload.keys()]), |
4952 | | - }) |
4953 | | - |
4954 | | - return payload, debug |
| 4892 | + return payload |
4955 | 4893 |
|
4956 | 4894 |
|
4957 | 4895 | @app.post("/api/payments/nowpayments/ipn") |
4958 | 4896 | def nowpayments_ipn_receive(): |
4959 | | - payload, payload_debug = parse_nowpayments_ipn_payload() |
| 4897 | + payload = parse_nowpayments_ipn_payload() |
4960 | 4898 | bypass_enabled = str(os.getenv("NOWPAYMENTS_IPN_TEST_BYPASS", "0")).strip().lower() in {"1", "true", "yes", "on"} |
4961 | 4899 | bypass_requested = str(request.args.get("test_bypass") or "").strip().lower() in {"1", "true", "yes", "on"} |
4962 | 4900 | has_identifier = any(normalize_text(payload.get(key), 120) for key in ("order_id", "invoice_id", "invoiceId", "payment_id", "id", "purchase_id")) |
4963 | 4901 | payment_status_value = normalize_text(payload.get("payment_status") or payload.get("status"), 80) |
4964 | 4902 | if not has_identifier or not payment_status_value: |
4965 | | - details = { |
4966 | | - "has_identifier": bool(has_identifier), |
4967 | | - "has_payment_status": bool(payment_status_value), |
4968 | | - } |
4969 | | - if bypass_enabled and bypass_requested: |
4970 | | - details["debug_payload"] = payload_debug |
4971 | 4903 | raise ApiError( |
4972 | 4904 | "NOWPayments IPN payload is missing required fields.", |
4973 | 4905 | 400, |
4974 | 4906 | code="NOWPAYMENTS_IPN_PAYLOAD_INVALID", |
4975 | | - details=details, |
| 4907 | + details={ |
| 4908 | + "has_identifier": bool(has_identifier), |
| 4909 | + "has_payment_status": bool(payment_status_value), |
| 4910 | + }, |
4976 | 4911 | ) |
4977 | 4912 | if not (bypass_enabled and bypass_requested): |
4978 | 4913 | signature = str(request.headers.get("x-nowpayments-sig") or request.headers.get("X-NOWPAYMENTS-SIG") or "").strip() |
@@ -5016,8 +4951,6 @@ def nowpayments_ipn_receive(): |
5016 | 4951 | "invoice_id": normalize_text(payload.get("invoice_id") or payload.get("invoiceId"), 120), |
5017 | 4952 | "payment_status": normalize_text(payload.get("payment_status") or payload.get("status"), 80), |
5018 | 4953 | "payload_keys": sorted([str(k) for k in payload.keys()]), |
5019 | | - "raw_len": payload_debug.get("raw_len", 0), |
5020 | | - "content_type": payload_debug.get("content_type", ""), |
5021 | 4954 | "app_version": APP_VERSION, |
5022 | 4955 | } |
5023 | 4956 | return jsonify(response_payload) |
|
0 commit comments