Files
Ai_Admin/WebAdmin/paypal_webhook.py

238 lines
11 KiB
Python
Raw Normal View History

2024-09-23 09:56:57 +00:00
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": {}})