Ai_Admin/WebAdmin/paypal_webhook.py
2024-09-23 09:56:57 +00:00

238 lines
11 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import requests
from django.conf import settings
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from datetime import datetime
from .models import Order
import json
from .base import logger
def get_paypal_access_token():
"""获取 PayPal 访问令牌"""
print("正在获取 PayPal 访问令牌...")
url = f"https://api.paypal.com/v1/oauth2/token"
auth = (
settings.PAYPAL_CLIENT_ID_SANDBOX if settings.PAYPAL_MODE == 'sandbox' else settings.PAYPAL_CLIENT_ID_PRODUCTION,
settings.PAYPAL_SECRET_KEY_SANDBOX if settings.PAYPAL_MODE == 'sandbox' else settings.PAYPAL_SECRET_KEY_PRODUCTION
)
headers = {
"Accept": "application/json",
"Accept-Language": "en_US"
}
data = {
"grant_type": "client_credentials"
}
response = requests.post(url, headers=headers, data=data, auth=auth)
if response.status_code == 200:
access_token = response.json().get('access_token')
logger.info(f"获取的访问令牌: {access_token}")
print(f"获取的访问令牌: {access_token}")
return access_token
else:
logger.error(f"获取 PayPal 访问令牌失败: {response.text}")
print(f"获取 PayPal 访问令牌失败: {response.text}")
return None
def verify_paypal_webhook(headers, body):
"""使用 PayPal API 验证 Webhook 签名"""
logger.info("验证 PayPal Webhook 签名...")
print("验证 PayPal Webhook 签名...")
verify_url = f"https://api.paypal.com/v1/notifications/verify-webhook-signature"
auth_token = get_paypal_access_token()
if not auth_token:
logger.error("无法获取 PayPal 访问令牌,无法验证 Webhook 签名")
print("无法获取 PayPal 访问令牌,无法验证 Webhook 签名")
return False
verify_headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {auth_token}"
}
verify_payload = {
"auth_algo": headers.get('Paypal-Auth-Algo'),
"cert_url": headers.get('Paypal-Cert-Url'),
"transmission_id": headers.get('Paypal-Transmission-Id'),
"transmission_sig": headers.get('Paypal-Transmission-Sig'),
"transmission_time": headers.get('Paypal-Transmission-Time'),
"webhook_id": settings.PAYPAL_WEBHOOK_ID,
"webhook_event": json.loads(body)
}
response = requests.post(verify_url, headers=verify_headers, json=verify_payload)
if response.status_code == 200 and response.json().get('verification_status') == 'SUCCESS':
logger.info("Webhook 签名验证成功")
print("Webhook 签名验证成功")
return True
else:
logger.error(f"Webhook 签名验证失败: {response.text}")
print(f"Webhook 签名验证失败: {response.text}")
return False
@csrf_exempt
def execute_paypal_payment(payment_id, payer_id):
"""执行 PayPal 支付"""
try:
logger.info(f"回调接收到 开始执行支付订单ID: {payment_id}, 付款人ID: {payer_id}")
order = Order.objects.get(order_id=payment_id)
if order.status == 'completed':
logger.info(f"订单 {payment_id} 已经支付,不需要再次执行支付")
print(f"订单 {payment_id} 已经支付,不需要再次执行支付")
return JsonResponse({"code": 400, "message": "订单已支付", "data": {}})
# 获取 PayPal 访问令牌
access_token = get_paypal_access_token()
if not access_token:
logger.error("无法获取支付访问令牌")
return JsonResponse({"code": 500, "message": "无法获取支付访问令牌", "data": {}})
# 根据配置文件选择 PayPal URL
paypal_base_url = "https://api.sandbox.paypal.com" if settings.PAYPAL_MODE == 'sandbox' else "https://api.paypal.com"
url = f"{paypal_base_url}/v1/payments/payment/{payment_id}/execute"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}"
}
payload = {
"payer_id": payer_id
}
logger.info(f"执行 PayPal 支付请求, URL: {url}, 请求体: {payload}")
print(f"执行 PayPal 支付请求, URL: {url}, 请求体: {payload}")
response = requests.post(url, headers=headers, json=payload)
# 处理支付执行结果
if response.status_code == 200:
order.status = 'completed'
order.updated_at = datetime.now()
order.save()
# 更新用户积分
user = order.user
user.points += order.plan.credits_per_month
user.save()
logger.info(f"订单 {payment_id} 支付成功,用户积分已更新: {order.plan.credits_per_month}")
print(f"订单 {payment_id} 支付成功,用户积分已更新: {order.plan.credits_per_month}")
return JsonResponse({"code": 200, "message": "支付成功", "data": {}})
else:
logger.error(f"支付执行失败: {response.text}")
print(f"支付执行失败: {response.text}")
return JsonResponse({"code": 400, "message": "支付执行失败", "data": response.json()})
except Order.DoesNotExist:
logger.error(f"订单 {payment_id} 不存在")
print(f"订单 {payment_id} 不存在")
return JsonResponse({"code": 400, "message": "订单不存在", "data": {}})
except Exception as e:
logger.error(f"支付执行过程中发生异常: {str(e)}")
print(f"支付执行过程中发生异常: {str(e)}")
return JsonResponse({"code": 500, "message": f"支付执行过程中发生异常: {str(e)}", "data": {}})
@csrf_exempt
def paypal_webhook(request):
"""PayPal Webhook 处理函数,用于接收和处理 PayPal 异步通知"""
try:
logger.info("接收到 PayPal Webhook 请求...")
print("接收到 PayPal Webhook 请求...")
headers = request.headers
payload = json.loads(request.body)
# 验证 Webhook 签名
if not verify_paypal_webhook(headers, request.body):
logger.error("Webhook 签名验证失败")
return JsonResponse({"code": 400, "message": "Webhook 签名验证失败", "data": {}})
event_type = payload.get('event_type')
# 打印接收到的 Webhook payload 以供调试
logger.info(f"接收到 PayPal Webhook 事件: {event_type}")
print(f"接收到 PayPal Webhook 事件: {event_type}")
logger.info(f"Webhook payload: {json.dumps(payload, indent=2)}")
# 处理不同类型的事件
if event_type == 'PAYMENT.SALE.COMPLETED':
parent_payment = payload['resource'].get('parent_payment') # 使用 parent_payment 作为订单 ID
state = payload['resource'].get('state') # 获取支付状态
if parent_payment:
try:
order = Order.objects.get(order_id=parent_payment)
# 通过 state 判断支付是否完成
if state == 'completed':
if order.status == 'completed':
logger.info(f"订单 {parent_payment} 已经完成,不重复更新")
print(f"订单 {parent_payment} 已经完成,不重复更新")
return JsonResponse({"code": 200, "message": "订单已处理", "data": {}})
# 更新订单状态为已完成
order.status = 'completed'
order.updated_at = datetime.now()
order.save()
# 更新用户积分
user = order.user
user.points += order.plan.credits_per_month
user.save()
logger.info(f"订单 {parent_payment} 状态更新为 'completed' 并已更新用户积分")
print(f"订单 {parent_payment} 状态更新为 'completed' 并已更新用户积分")
return JsonResponse({"code": 200, "message": "支付完成,订单更新成功", "data": {}})
else:
logger.error(f"支付状态为 {state},未完成支付")
print(f"支付状态为 {state},未完成支付")
return JsonResponse({"code": 400, "message": f"支付状态为 {state},未完成支付", "data": {}})
except Order.DoesNotExist:
logger.error(f"未找到订单 {parent_payment}")
print(f"未找到订单 {parent_payment}")
return JsonResponse({"code": 400, "message": "订单不存在", "data": {}})
else:
logger.error("Webhook payload 中缺少 parent_payment")
print("Webhook payload 中缺少 parent_payment")
return JsonResponse({"code": 400, "message": "Webhook payload 中缺少 parent_payment", "data": {}})
elif event_type == 'PAYMENTS.PAYMENT.CREATED':
# 支付创建时的逻辑,可以执行支付
payment_id = payload['resource']['id']
payer_id = payload['resource']['payer']['payer_info']['payer_id']
execute_paypal_payment(payment_id, payer_id)
elif event_type == 'PAYMENT.SALE.DENIED':
parent_payment = payload['resource'].get('parent_payment')
if parent_payment:
try:
order = Order.objects.get(order_id=parent_payment)
order.status = 'failed'
order.updated_at = datetime.now()
order.save()
logger.info(f"订单 {parent_payment} 状态更新为 'failed'")
print(f"订单 {parent_payment} 状态更新为 'failed'")
except Order.DoesNotExist:
logger.error(f"未找到订单 {parent_payment}")
print(f"未找到订单 {parent_payment}")
return JsonResponse({"code": 400, "message": "订单不存在", "data": {}})
return JsonResponse({"code": 200, "message": "Webhook 处理成功", "data": {}})
except json.JSONDecodeError:
logger.error("请求体不是有效的JSON")
print("请求体不是有效的JSON")
return JsonResponse({"code": 400, "message": "请求体不是有效的JSON", "data": {}})
except Exception as e:
logger.error(f"处理 PayPal Webhook 时发生错误: {str(e)}")
print(f"处理 PayPal Webhook 时发生错误: {str(e)}")
return JsonResponse({"code": 500, "message": f"处理 PayPal Webhook 时发生错误: {str(e)}", "data": {}})