|
|
@@ -965,102 +965,130 @@ async def now_webhook(request: Request, background_tasks: BackgroundTasks):
|
|
|
"""NOW Payments IPN (Instant Payment Notification) webhook.
|
|
|
Send email after successful payment confirmation.
|
|
|
"""
|
|
|
- main_logger.info("now-webhook...")
|
|
|
+ main_logger.info("now-webhook: Starting processing")
|
|
|
|
|
|
- payload = await request.json()
|
|
|
- sig_header = request.headers["x-nowpayments-sig"]
|
|
|
-
|
|
|
- if os.getenv("ENV") == "production":
|
|
|
- ipn_secret_key = os.getenv("NOWPAYMENTS_IPN")
|
|
|
- else:
|
|
|
- ipn_secret_key = os.getenv("NOWPAYMENTS_SANDBOX_IPN")
|
|
|
-
|
|
|
- is_verified = ipn_signature_check(ipn_secret_key, sig_header, payload)
|
|
|
-
|
|
|
- if is_verified:
|
|
|
- # check payment status, update order if
|
|
|
- # status has changed (finished, expired, failed)
|
|
|
- # send email if status: finished.
|
|
|
+ try:
|
|
|
+ payload = await request.json()
|
|
|
+ main_logger.info(f"now-webhook: Received payload with status: {payload.get('payment_status', 'unknown')}")
|
|
|
+
|
|
|
+ sig_header = request.headers["x-nowpayments-sig"]
|
|
|
|
|
|
- event_type = payload["payment_status"]
|
|
|
+ if os.getenv("ENV") == "production":
|
|
|
+ ipn_secret_key = os.getenv("NOWPAYMENTS_IPN")
|
|
|
+ else:
|
|
|
+ ipn_secret_key = os.getenv("NOWPAYMENTS_SANDBOX_IPN")
|
|
|
|
|
|
- if event_type in ["finished", "expired", "failed"]:
|
|
|
- if "payment_id" in payload:
|
|
|
- order_id = payload["order_id"]
|
|
|
+ is_verified = ipn_signature_check(ipn_secret_key, sig_header, payload)
|
|
|
|
|
|
- status_res = update_status_order(
|
|
|
- event_type,
|
|
|
- order_id,
|
|
|
- settings.git_repo,
|
|
|
- settings.local_db.filepath,
|
|
|
- )
|
|
|
+ if is_verified:
|
|
|
+ # check payment status, update order if
|
|
|
+ # status has changed (finished, expired, failed)
|
|
|
+ # send email if status: finished.
|
|
|
+ event_type = payload["payment_status"]
|
|
|
+ main_logger.info(f"now-webhook: Processing verified event type: {event_type}")
|
|
|
|
|
|
- if status_res is False:
|
|
|
- raise HTTPException(
|
|
|
- status_code=400, detail="Order could not be updated."
|
|
|
- )
|
|
|
+ if event_type in ["finished", "expired", "failed"]:
|
|
|
+ if "payment_id" in payload:
|
|
|
+ order_id = payload["order_id"]
|
|
|
+ main_logger.info(f"now-webhook: Processing order: {order_id}")
|
|
|
|
|
|
- if event_type == "finished":
|
|
|
- product_list = fetch_order_product_list(
|
|
|
- order_id, settings.git_repo, settings.local_db.filepath
|
|
|
+ status_res = update_status_order(
|
|
|
+ event_type,
|
|
|
+ order_id,
|
|
|
+ settings.git_repo,
|
|
|
+ settings.local_db.filepath,
|
|
|
)
|
|
|
|
|
|
- if product_list:
|
|
|
- for product_info in product_list:
|
|
|
- try:
|
|
|
- product = read_file(
|
|
|
- settings.git_repo,
|
|
|
- f"products/{product_info.filename}",
|
|
|
- settings.document_match,
|
|
|
- settings.block_types,
|
|
|
- )
|
|
|
-
|
|
|
- # Get the variation ID based on size and style
|
|
|
- product_variation_id = get_variation_id(product,
|
|
|
- product_info.size,
|
|
|
- product_info.style)
|
|
|
-
|
|
|
- update_product_inventory(
|
|
|
- product_info.filename,
|
|
|
- product_info.quantity,
|
|
|
- product_variation_id,
|
|
|
- settings,
|
|
|
- )
|
|
|
-
|
|
|
- except Exception as e:
|
|
|
- main_logger.error(f"now-webhook error: Failed to update inventory: {e}")
|
|
|
- # Continue processing other products
|
|
|
-
|
|
|
- # -- send email
|
|
|
- try:
|
|
|
- email_settings = {
|
|
|
- "git_repo": settings.git_repo,
|
|
|
- "document_match": settings.document_match,
|
|
|
- "block_types": settings.block_types,
|
|
|
- }
|
|
|
-
|
|
|
+ if status_res is False:
|
|
|
+ main_logger.error(f"now-webhook: Failed to update order status for {order_id}")
|
|
|
+ return JSONResponse(content={"success": False, "error": "Order could not be updated."}, status_code=400)
|
|
|
+
|
|
|
+ if event_type == "finished":
|
|
|
+ product_list = fetch_order_product_list(
|
|
|
+ order_id, settings.git_repo, settings.local_db.filepath
|
|
|
+ )
|
|
|
+
|
|
|
+ main_logger.info(f"now-webhook: Found {len(product_list) if product_list else 0} products to update")
|
|
|
+
|
|
|
+ if product_list:
|
|
|
+ updated_products = 0
|
|
|
+ for product_info in product_list:
|
|
|
+ try:
|
|
|
+ product = read_file(
|
|
|
+ settings.git_repo,
|
|
|
+ f"products/{product_info.filename}",
|
|
|
+ settings.document_match,
|
|
|
+ settings.block_types,
|
|
|
+ )
|
|
|
+
|
|
|
+ # Get the variation ID based on size and style
|
|
|
+ product_variation_id = get_variation_id(product,
|
|
|
+ product_info.size,
|
|
|
+ product_info.style)
|
|
|
+
|
|
|
+ main_logger.info(f"now-webhook: Updating inventory for {product_info.filename}, variation: {product_variation_id}, quantity: {product_info.quantity}")
|
|
|
+
|
|
|
+ update_product_inventory(
|
|
|
+ product_info.filename,
|
|
|
+ product_info.quantity,
|
|
|
+ product_variation_id,
|
|
|
+ settings,
|
|
|
+ )
|
|
|
+ updated_products += 1
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ main_logger.error(f"now-webhook error: Failed to update inventory for {product_info.filename}: {e}")
|
|
|
+ import traceback
|
|
|
+ main_logger.error(traceback.format_exc())
|
|
|
+ # Continue processing other products
|
|
|
+
|
|
|
+ main_logger.info(f"now-webhook: Successfully updated {updated_products} out of {len(product_list)} products")
|
|
|
+
|
|
|
+ # -- send email
|
|
|
try:
|
|
|
- order = prepare_email_order(order_id, email_settings)
|
|
|
- await send_email(email_settings, order, background_tasks)
|
|
|
- except TypeError as e:
|
|
|
- main_logger.warning(f"now-webhook warning: Email preparation issue: {e}")
|
|
|
-
|
|
|
- except Exception as e:
|
|
|
- main_logger.error(f"now-webhook error: Failed to send email: {e}")
|
|
|
+ email_settings = {
|
|
|
+ "git_repo": settings.git_repo,
|
|
|
+ "document_match": settings.document_match,
|
|
|
+ "block_types": settings.block_types,
|
|
|
+ }
|
|
|
|
|
|
- elif event_type == "failed":
|
|
|
- main_logger.error(
|
|
|
- f"(NowPayments) Unhandled event type {event_type}"
|
|
|
- )
|
|
|
- raise HTTPException(
|
|
|
- status_code=400, detail="NOWPayments payment error."
|
|
|
- )
|
|
|
+ try:
|
|
|
+ main_logger.info(f"now-webhook: Preparing email for order {order_id}")
|
|
|
+ order = prepare_email_order(order_id, email_settings)
|
|
|
+ main_logger.info(f"now-webhook: Sending email for order {order_id}")
|
|
|
+ await send_email(email_settings, order, background_tasks)
|
|
|
+ main_logger.info(f"now-webhook: Email queued successfully")
|
|
|
+ except TypeError as e:
|
|
|
+ main_logger.warning(f"now-webhook warning: Email preparation issue: {e}")
|
|
|
+ import traceback
|
|
|
+ main_logger.error(traceback.format_exc())
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ main_logger.error(f"now-webhook error: Failed to send email: {e}")
|
|
|
+ import traceback
|
|
|
+ main_logger.error(traceback.format_exc())
|
|
|
+
|
|
|
+ elif event_type == "failed":
|
|
|
+ main_logger.error(f"now-webhook: Payment failed for order {order_id}")
|
|
|
+ # We don't want to raise an exception here since we need to return a success response
|
|
|
+ # Just log the error
|
|
|
+ else:
|
|
|
+ main_logger.error("now-webhook: payment_id not in payload")
|
|
|
+ return JSONResponse(content={"success": False, "error": "Payment ID missing"}, status_code=400)
|
|
|
else:
|
|
|
- main_logger.error("(NowPayments) payment_id not in payload")
|
|
|
- raise HTTPException(
|
|
|
- status_code=400, detail="NOWPayments payment error."
|
|
|
- )
|
|
|
-
|
|
|
+ main_logger.info(f"now-webhook: Event type {event_type} not processed (not in finished/expired/failed)")
|
|
|
+ else:
|
|
|
+ main_logger.error("now-webhook: Signature verification failed")
|
|
|
+ return JSONResponse(content={"success": False, "error": "Signature verification failed"}, status_code=400)
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ main_logger.error(f"now-webhook error: Unhandled exception: {e}")
|
|
|
+ import traceback
|
|
|
+ main_logger.error(traceback.format_exc())
|
|
|
+ # Return success anyway to prevent NOW Payments from retrying constantly
|
|
|
+
|
|
|
+ main_logger.info("now-webhook: Processing completed")
|
|
|
+ return JSONResponse(content={"success": True}, status_code=200)
|
|
|
|
|
|
@app.get("/checkout/success", response_class=HTMLResponse)
|
|
|
async def checkout_success(request: Request):
|