Skip to content

Commit 7341878

Browse files
Update app.py
1 parent 84810a6 commit 7341878

1 file changed

Lines changed: 182 additions & 21 deletions

File tree

app.py

Lines changed: 182 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,12 @@ def ensure_parent_dir(path_value: str) -> None:
232232
NOWPAYMENTS_POSTPAY_NEXT_STEP_HOURS = int(os.getenv("NOWPAYMENTS_POSTPAY_NEXT_STEP_HOURS", "24"))
233233
NOWPAYMENTS_POSTPAY_REPLY_TO = os.getenv("NOWPAYMENTS_POSTPAY_REPLY_TO", RESEND_TO or "hello@payeeproof.com").strip()
234234
NOWPAYMENTS_SUPPORT_EMAIL = os.getenv("NOWPAYMENTS_SUPPORT_EMAIL", RESEND_TO or "hello@payeeproof.com").strip()
235+
NOWPAYMENTS_POSTPAY_CRM_ENABLED = str(os.getenv("NOWPAYMENTS_POSTPAY_CRM_ENABLED", "1")).strip().lower() not in {"0", "false", "no", "off"}
236+
NOWPAYMENTS_POSTPAY_CRM_URL = os.getenv("NOWPAYMENTS_POSTPAY_CRM_URL", CRM_INTAKE_URL).strip()
237+
NOWPAYMENTS_POSTPAY_CRM_SECRET = os.getenv("NOWPAYMENTS_POSTPAY_CRM_SECRET", CRM_INTAKE_SECRET).strip()
238+
NOWPAYMENTS_POSTPAY_CRM_AUTH_HEADER = os.getenv("NOWPAYMENTS_POSTPAY_CRM_AUTH_HEADER", CRM_INTAKE_AUTH_HEADER or "X-PayeeProof-CRM-Secret").strip() or "X-PayeeProof-CRM-Secret"
239+
NOWPAYMENTS_POSTPAY_CRM_TIMEOUT = float(os.getenv("NOWPAYMENTS_POSTPAY_CRM_TIMEOUT_SEC", str(CRM_INTAKE_TIMEOUT or 6)))
240+
NOWPAYMENTS_POSTPAY_CRM_EVENT_NAME = os.getenv("NOWPAYMENTS_POSTPAY_CRM_EVENT_NAME", "payment_confirmed").strip() or "payment_confirmed"
235241
DEFAULT_NOWPAYMENTS_PRODUCTS = {
236242
"pilot_399": {
237243
"amount_usd": 399.0,
@@ -1933,8 +1939,8 @@ def store_nowpayments_order_created(*, order_id: str, product: Dict[str, Any], c
19331939
order_id, created_at, updated_at, status, payment_status, fulfillment_status,
19341940
customer_email, product_sku, product_title, amount_usd, price_currency, order_description,
19351941
provider_invoice_id, invoice_url, success_url, cancel_url, source_ip, origin, raw_create_json,
1936-
payment_notice_status, customer_receipt_status
1937-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1942+
payment_notice_status, customer_receipt_status, crm_status
1943+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
19381944
""",
19391945
(
19401946
order_id,
@@ -1958,6 +1964,7 @@ def store_nowpayments_order_created(*, order_id: str, product: Dict[str, Any], c
19581964
json_dumps_safe(provider_response),
19591965
"pending",
19601966
"pending" if customer_email else "skipped",
1967+
"pending",
19611968
),
19621969
)
19631970
conn.commit()
@@ -1975,6 +1982,18 @@ def _nowpayments_paid_flag(status: str) -> bool:
19751982
return str(status or "").strip().lower() in {"finished", "confirmed"}
19761983

19771984

1985+
def _nowpayments_terminal_status(status: Any) -> bool:
1986+
return str(status or "").strip().lower() in {"sent", "skipped", "disabled", "not_configured"}
1987+
1988+
1989+
def _nowpayments_postpay_complete(admin_status: Any, customer_status: Any, crm_status: Any) -> bool:
1990+
return all([
1991+
_nowpayments_terminal_status(admin_status),
1992+
_nowpayments_terminal_status(customer_status),
1993+
_nowpayments_terminal_status(crm_status),
1994+
])
1995+
1996+
19781997
def send_nowpayments_internal_paid_email(order: Dict[str, Any]) -> Dict[str, Optional[str]]:
19791998
if not NOWPAYMENTS_POSTPAY_EMAILS_ENABLED:
19801999
return {"status": "disabled", "email_id": None, "debug_detail": None}
@@ -2119,9 +2138,89 @@ def send_nowpayments_customer_paid_email(order: Dict[str, Any]) -> Dict[str, Opt
21192138
return {"status": "failed", "email_id": None, "debug_detail": f"RESEND_JSON_ERROR: {exc}"}
21202139

21212140

2122-
def update_nowpayments_postpay_delivery(order_id: str, admin_result: Dict[str, Optional[str]], customer_result: Dict[str, Optional[str]]) -> Dict[str, Any]:
2141+
2142+
def send_nowpayments_crm_intake(order: Dict[str, Any]) -> Dict[str, Optional[str]]:
2143+
if not NOWPAYMENTS_POSTPAY_CRM_ENABLED:
2144+
return {"status": "disabled", "delivery_id": None, "debug_detail": None, "response_code": None}
2145+
if not NOWPAYMENTS_POSTPAY_CRM_URL:
2146+
return {"status": "not_configured", "delivery_id": None, "debug_detail": None, "response_code": None}
2147+
order_id = normalize_text(order.get("order_id"), 120)
2148+
crm_payload = {
2149+
"event": NOWPAYMENTS_POSTPAY_CRM_EVENT_NAME,
2150+
"product": "payeeproof",
2151+
"payment": {
2152+
"order_id": order_id,
2153+
"created_at": order.get("created_at") or "",
2154+
"updated_at": order.get("updated_at") or "",
2155+
"product_sku": order.get("product_sku") or "",
2156+
"product_title": order.get("product_title") or "",
2157+
"amount_usd": order.get("amount_usd"),
2158+
"payment_status": order.get("payment_status") or "",
2159+
"fulfillment_status": order.get("fulfillment_status") or "",
2160+
"provider_invoice_id": order.get("provider_invoice_id") or "",
2161+
"provider_payment_id": order.get("provider_payment_id") or "",
2162+
"invoice_url": order.get("invoice_url") or "",
2163+
"pay_currency": order.get("pay_currency") or "",
2164+
"pay_amount": order.get("pay_amount") or order.get("actually_paid") or "",
2165+
"customer_email": order.get("customer_email") or "",
2166+
},
2167+
"meta": {
2168+
"origin": order.get("origin") or "",
2169+
"source_ip": order.get("source_ip") or "",
2170+
"support_email": NOWPAYMENTS_SUPPORT_EMAIL,
2171+
"next_step_hours": NOWPAYMENTS_POSTPAY_NEXT_STEP_HOURS,
2172+
},
2173+
"links": {
2174+
"site": "https://payeeproof.com",
2175+
"api_base": PUBLIC_API_BASE,
2176+
"success_url": order.get("success_url") or NOWPAYMENTS_SUCCESS_URL,
2177+
},
2178+
}
2179+
headers = {
2180+
"Content-Type": "application/json",
2181+
"Accept": "application/json",
2182+
"User-Agent": "PayeeProof/1.0 (+https://payeeproof.com)",
2183+
"Idempotency-Key": f"crm-payment-{order_id or uuid.uuid4().hex[:12]}",
2184+
}
2185+
if NOWPAYMENTS_POSTPAY_CRM_SECRET:
2186+
headers[NOWPAYMENTS_POSTPAY_CRM_AUTH_HEADER] = NOWPAYMENTS_POSTPAY_CRM_SECRET
2187+
try:
2188+
response = requests.post(
2189+
NOWPAYMENTS_POSTPAY_CRM_URL,
2190+
headers=headers,
2191+
json=crm_payload,
2192+
timeout=NOWPAYMENTS_POSTPAY_CRM_TIMEOUT,
2193+
)
2194+
raw_text = response.text[:1000]
2195+
if response.status_code < 200 or response.status_code >= 300:
2196+
return {
2197+
"status": "failed",
2198+
"delivery_id": order_id,
2199+
"debug_detail": f"CRM_HTTP_{response.status_code}: {raw_text}",
2200+
"response_code": response.status_code,
2201+
}
2202+
return {
2203+
"status": "sent",
2204+
"delivery_id": order_id,
2205+
"debug_detail": None,
2206+
"response_code": response.status_code,
2207+
}
2208+
except requests.RequestException as exc:
2209+
return {
2210+
"status": "failed",
2211+
"delivery_id": order_id,
2212+
"debug_detail": f"CRM_REQUEST_ERROR: {exc}",
2213+
"response_code": None,
2214+
}
2215+
2216+
2217+
def update_nowpayments_postpay_delivery(order_id: str, admin_result: Dict[str, Optional[str]], customer_result: Dict[str, Optional[str]], crm_result: Dict[str, Optional[str]]) -> Dict[str, Any]:
21232218
order_id = normalize_text(order_id, 120)
21242219
updated_at = utc_now_iso()
2220+
admin_status = str(admin_result.get("status") or "unknown")
2221+
customer_status = str(customer_result.get("status") or "unknown")
2222+
crm_status = str(crm_result.get("status") or "unknown")
2223+
fulfillment_status = "postpay_completed" if _nowpayments_postpay_complete(admin_status, customer_status, crm_status) else "payment_confirmed"
21252224
conn = get_db()
21262225
try:
21272226
db_execute(
@@ -2138,20 +2237,32 @@ def update_nowpayments_postpay_delivery(order_id: str, admin_result: Dict[str, O
21382237
customer_receipt_sent_at = ?,
21392238
customer_receipt_email_id = ?,
21402239
customer_receipt_debug = ?,
2240+
crm_status = ?,
2241+
crm_delivery_id = ?,
2242+
crm_error = ?,
2243+
crm_last_attempt_at = ?,
2244+
crm_response_code = ?,
2245+
crm_recorded_at = ?,
21412246
postpay_processed_at = ?
21422247
WHERE order_id = ?
21432248
""",
21442249
(
21452250
updated_at,
2146-
"payment_confirmed",
2147-
str(admin_result.get("status") or "unknown"),
2148-
updated_at if str(admin_result.get("status") or "") == "sent" else None,
2251+
fulfillment_status,
2252+
admin_status,
2253+
updated_at if admin_status == "sent" else None,
21492254
normalize_text(admin_result.get("email_id"), 120),
21502255
normalize_text(admin_result.get("debug_detail"), 500),
2151-
str(customer_result.get("status") or "unknown"),
2152-
updated_at if str(customer_result.get("status") or "") == "sent" else None,
2256+
customer_status,
2257+
updated_at if customer_status == "sent" else None,
21532258
normalize_text(customer_result.get("email_id"), 120),
21542259
normalize_text(customer_result.get("debug_detail"), 500),
2260+
crm_status,
2261+
normalize_text(crm_result.get("delivery_id"), 120),
2262+
normalize_text(crm_result.get("debug_detail"), 500),
2263+
updated_at,
2264+
crm_result.get("response_code"),
2265+
updated_at if crm_status == "sent" else None,
21552266
updated_at,
21562267
order_id,
21572268
),
@@ -2169,18 +2280,36 @@ def maybe_process_nowpayments_paid_order(order_id: str) -> Dict[str, Any]:
21692280
return order
21702281
admin_status = str(order.get("payment_notice_status") or "").strip().lower()
21712282
customer_status = str(order.get("customer_receipt_status") or "").strip().lower()
2283+
crm_status = str(order.get("crm_status") or "").strip().lower()
21722284
customer_email = normalize_text(order.get("customer_email"), 200).lower()
21732285
needs_admin = admin_status not in {"sent", "not_configured", "disabled"}
21742286
needs_customer = bool(customer_email) and customer_status not in {"sent", "skipped", "not_configured", "disabled"}
2175-
if not needs_admin and not needs_customer:
2287+
needs_crm = crm_status not in {"sent", "not_configured", "disabled"}
2288+
if not needs_admin and not needs_customer and not needs_crm:
21762289
return order
2177-
admin_result = {"status": admin_status or ("disabled" if not NOWPAYMENTS_POSTPAY_EMAILS_ENABLED else "not_configured" if not (RESEND_API_KEY and RESEND_FROM and RESEND_TO) else "skipped"), "email_id": order.get("payment_notice_email_id"), "debug_detail": order.get("payment_notice_debug")}
2178-
customer_result = {"status": customer_status or ("skipped" if not customer_email else "disabled" if not NOWPAYMENTS_POSTPAY_EMAILS_ENABLED else "not_configured" if not (RESEND_API_KEY and RESEND_FROM) else "skipped"), "email_id": order.get("customer_receipt_email_id"), "debug_detail": order.get("customer_receipt_debug")}
2290+
admin_result = {
2291+
"status": admin_status or ("disabled" if not NOWPAYMENTS_POSTPAY_EMAILS_ENABLED else "not_configured" if not (RESEND_API_KEY and RESEND_FROM and RESEND_TO) else "skipped"),
2292+
"email_id": order.get("payment_notice_email_id"),
2293+
"debug_detail": order.get("payment_notice_debug"),
2294+
}
2295+
customer_result = {
2296+
"status": customer_status or ("skipped" if not customer_email else "disabled" if not NOWPAYMENTS_POSTPAY_EMAILS_ENABLED else "not_configured" if not (RESEND_API_KEY and RESEND_FROM) else "skipped"),
2297+
"email_id": order.get("customer_receipt_email_id"),
2298+
"debug_detail": order.get("customer_receipt_debug"),
2299+
}
2300+
crm_result = {
2301+
"status": crm_status or ("disabled" if not NOWPAYMENTS_POSTPAY_CRM_ENABLED else "not_configured" if not NOWPAYMENTS_POSTPAY_CRM_URL else "skipped"),
2302+
"delivery_id": order.get("crm_delivery_id"),
2303+
"debug_detail": order.get("crm_error"),
2304+
"response_code": order.get("crm_response_code"),
2305+
}
21792306
if needs_admin:
21802307
admin_result = send_nowpayments_internal_paid_email(order)
21812308
if needs_customer:
21822309
customer_result = send_nowpayments_customer_paid_email(order)
2183-
return update_nowpayments_postpay_delivery(order_id, admin_result, customer_result)
2310+
if needs_crm:
2311+
crm_result = send_nowpayments_crm_intake(order)
2312+
return update_nowpayments_postpay_delivery(order_id, admin_result, customer_result, crm_result)
21842313

21852314

21862315
def upsert_nowpayments_order_from_ipn(payload: Dict[str, Any]) -> Dict[str, Any]:
@@ -2236,8 +2365,8 @@ def upsert_nowpayments_order_from_ipn(payload: Dict[str, Any]) -> Dict[str, Any]
22362365
order_id, created_at, updated_at, status, payment_status, fulfillment_status,
22372366
provider_invoice_id, provider_payment_id, pay_currency, pay_amount, actually_paid, actually_paid_at_fiat,
22382367
purchase_id, raw_last_ipn_json, last_ipn_at,
2239-
payment_notice_status, customer_receipt_status
2240-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2368+
payment_notice_status, customer_receipt_status, crm_status
2369+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
22412370
""",
22422371
(
22432372
resolved_order_id,
@@ -2257,6 +2386,7 @@ def upsert_nowpayments_order_from_ipn(payload: Dict[str, Any]) -> Dict[str, Any]
22572386
updated_at,
22582387
"pending",
22592388
"pending",
2389+
"pending",
22602390
),
22612391
)
22622392
conn.commit()
@@ -2282,11 +2412,21 @@ def nowpayments_public_order_view(order: Dict[str, Any]) -> Dict[str, Any]:
22822412
paid = _nowpayments_paid_flag(str(order.get("payment_status") or ""))
22832413
customer_email = normalize_text(order.get("customer_email"), 200).lower()
22842414
support_email = NOWPAYMENTS_SUPPORT_EMAIL or RESEND_TO or RESEND_FROM
2415+
internal_alert_status = order.get("payment_notice_status") or "pending"
2416+
customer_receipt_status = order.get("customer_receipt_status") or ("skipped" if not customer_email else "pending")
2417+
crm_status = order.get("crm_status") or "pending"
2418+
postpay_complete = _nowpayments_postpay_complete(internal_alert_status, customer_receipt_status, crm_status)
2419+
if paid and postpay_complete:
2420+
message = "Payment confirmed. Confirmation flow is complete."
2421+
elif paid:
2422+
message = "Payment confirmed. Finalizing confirmation flow now."
2423+
else:
2424+
message = "Payment is not confirmed yet. If you already paid, wait a few seconds and refresh."
22852425
return {
22862426
"order_id": order.get("order_id"),
22872427
"status": order.get("status") or "created",
22882428
"payment_status": order.get("payment_status") or "waiting",
2289-
"fulfillment_status": order.get("fulfillment_status") or ("payment_confirmed" if paid else "pending_payment"),
2429+
"fulfillment_status": order.get("fulfillment_status") or ("postpay_completed" if postpay_complete else "payment_confirmed" if paid else "pending_payment"),
22902430
"paid": paid,
22912431
"product": {
22922432
"sku": order.get("product_sku") or "",
@@ -2297,13 +2437,14 @@ def nowpayments_public_order_view(order: Dict[str, Any]) -> Dict[str, Any]:
22972437
"provider_invoice_id": order.get("provider_invoice_id") or "",
22982438
"updated_at": order.get("updated_at") or order.get("created_at") or "",
22992439
"customer_email_present": bool(customer_email),
2300-
"payment_notice_status": order.get("payment_notice_status") or "pending",
2301-
"customer_receipt_status": order.get("customer_receipt_status") or ("skipped" if not customer_email else "pending"),
2440+
"internal_alert_status": internal_alert_status,
2441+
"payment_notice_status": internal_alert_status,
2442+
"customer_receipt_status": customer_receipt_status,
2443+
"crm_status": crm_status,
2444+
"postpay_complete": postpay_complete,
2445+
"journal_status": "recorded" if paid else "pending",
23022446
"support_email": support_email,
2303-
"message": (
2304-
f"Payment confirmed. We will follow up within about {NOWPAYMENTS_POSTPAY_NEXT_STEP_HOURS} hours." if paid
2305-
else "Payment is not confirmed yet. If you already paid, wait a few seconds and refresh."
2306-
),
2447+
"message": message,
23072448
}
23082449

23092450

@@ -4249,6 +4390,12 @@ def ensure_db() -> None:
42494390
customer_receipt_sent_at TEXT,
42504391
customer_receipt_email_id TEXT,
42514392
customer_receipt_debug TEXT,
4393+
crm_status TEXT,
4394+
crm_delivery_id TEXT,
4395+
crm_error TEXT,
4396+
crm_last_attempt_at TEXT,
4397+
crm_response_code INTEGER,
4398+
crm_recorded_at TEXT,
42524399
postpay_processed_at TEXT
42534400
)
42544401
"""
@@ -4294,6 +4441,12 @@ def ensure_db() -> None:
42944441
customer_receipt_sent_at TEXT,
42954442
customer_receipt_email_id TEXT,
42964443
customer_receipt_debug TEXT,
4444+
crm_status TEXT,
4445+
crm_delivery_id TEXT,
4446+
crm_error TEXT,
4447+
crm_last_attempt_at TEXT,
4448+
crm_response_code INTEGER,
4449+
crm_recorded_at TEXT,
42974450
postpay_processed_at TEXT
42984451
)
42994452
"""
@@ -4333,6 +4486,12 @@ def ensure_db() -> None:
43334486
ensure_column(conn, "nowpayments_orders", "customer_receipt_sent_at", "TEXT")
43344487
ensure_column(conn, "nowpayments_orders", "customer_receipt_email_id", "TEXT")
43354488
ensure_column(conn, "nowpayments_orders", "customer_receipt_debug", "TEXT")
4489+
ensure_column(conn, "nowpayments_orders", "crm_status", "TEXT")
4490+
ensure_column(conn, "nowpayments_orders", "crm_delivery_id", "TEXT")
4491+
ensure_column(conn, "nowpayments_orders", "crm_error", "TEXT")
4492+
ensure_column(conn, "nowpayments_orders", "crm_last_attempt_at", "TEXT")
4493+
ensure_column(conn, "nowpayments_orders", "crm_response_code", "INTEGER")
4494+
ensure_column(conn, "nowpayments_orders", "crm_recorded_at", "TEXT")
43364495
ensure_column(conn, "nowpayments_orders", "postpay_processed_at", "TEXT")
43374496
db_execute(conn, "CREATE UNIQUE INDEX IF NOT EXISTS idx_nowpayments_orders_order_id ON nowpayments_orders(order_id)")
43384497
db_execute(conn, "CREATE INDEX IF NOT EXISTS idx_nowpayments_orders_payment_id ON nowpayments_orders(provider_payment_id)")
@@ -4710,6 +4869,7 @@ def nowpayments_ipn_receive():
47104869
"paid": bool(updated.get("paid")),
47114870
"payment_notice_status": (processed_order or {}).get("payment_notice_status") or "pending",
47124871
"customer_receipt_status": (processed_order or {}).get("customer_receipt_status") or "pending",
4872+
"crm_status": (processed_order or {}).get("crm_status") or "pending",
47134873
},
47144874
)
47154875
return jsonify({
@@ -4720,6 +4880,7 @@ def nowpayments_ipn_receive():
47204880
"paid": bool(updated.get("paid")),
47214881
"payment_notice_status": (processed_order or {}).get("payment_notice_status") or "pending",
47224882
"customer_receipt_status": (processed_order or {}).get("customer_receipt_status") or "pending",
4883+
"crm_status": (processed_order or {}).get("crm_status") or "pending",
47234884
})
47244885

47254886

0 commit comments

Comments
 (0)