166 lines
7.5 KiB
Python
Executable File
166 lines
7.5 KiB
Python
Executable File
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
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def get_paypal_access_token():
|
|
"""获取 PayPal 访问令牌"""
|
|
print("正在获取 PayPal 访问令牌...")
|
|
url = f"https://api.{settings.PAYPAL_MODE}.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')
|
|
print(f"获取的访问令牌: {access_token}")
|
|
return access_token
|
|
else:
|
|
print(f"获取 PayPal 访问令牌失败: {response.text}")
|
|
return None
|
|
|
|
def verify_paypal_webhook(headers, body):
|
|
"""使用 PayPal API 验证 Webhook 签名"""
|
|
print("验证 PayPal Webhook 签名...")
|
|
verify_url = f"https://api.{settings.PAYPAL_MODE}.paypal.com/v1/notifications/verify-webhook-signature"
|
|
auth_token = get_paypal_access_token()
|
|
if not auth_token:
|
|
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':
|
|
print("Webhook 签名验证成功")
|
|
return True
|
|
else:
|
|
print(f"Webhook 签名验证失败: {response.text}")
|
|
return False
|
|
|
|
@csrf_exempt
|
|
def paypal_webhook(request):
|
|
"""PayPal Webhook 处理函数,用于接收和处理 PayPal 异步通知"""
|
|
try:
|
|
print("接收到 PayPal Webhook 请求...")
|
|
headers = request.headers
|
|
payload = json.loads(request.body)
|
|
|
|
# 验证 Webhook 签名
|
|
if not verify_paypal_webhook(headers, request.body):
|
|
return JsonResponse({"code": 400, "message": "Webhook 签名验证失败", "data": {}})
|
|
|
|
event_type = payload.get('event_type')
|
|
|
|
# 打印接收到的 Webhook payload 以供调试
|
|
print(f"接收到 PayPal Webhook 事件: {event_type}")
|
|
print(f"Webhook payload: {json.dumps(payload, indent=2)}")
|
|
|
|
# 处理不同类型的事件
|
|
if event_type == 'PAYMENT.SALE.COMPLETED':
|
|
sale_id = payload['resource'].get('id')
|
|
parent_payment = payload['resource'].get('parent_payment') # 使用parent_payment作为订单ID
|
|
if parent_payment:
|
|
try:
|
|
order = Order.objects.get(order_id=parent_payment)
|
|
|
|
# 检查订单状态,防止重复更新
|
|
if order.status == 'completed':
|
|
print(f"订单 {parent_payment} 已经完成,不重复更新")
|
|
return JsonResponse({"code": 200, "message": "订单已处理", "data": {}})
|
|
|
|
# 再次向 PayPal 查询订单状态,确保支付确实完成
|
|
access_token = get_paypal_access_token()
|
|
if not access_token:
|
|
print("无法获取 PayPal 访问令牌,无法验证订单状态")
|
|
return JsonResponse({"code": 500, "message": "无法获取支付访问令牌", "data": {}})
|
|
|
|
payment_details_url = f"https://api.{settings.PAYPAL_MODE}.paypal.com/v1/payments/payment/{parent_payment}"
|
|
headers = {
|
|
"Content-Type": "application/json",
|
|
"Authorization": f"Bearer {access_token}"
|
|
}
|
|
payment_details_response = requests.get(payment_details_url, headers=headers)
|
|
|
|
if payment_details_response.status_code == 200:
|
|
payment_details = payment_details_response.json()
|
|
if payment_details['state'] == 'approved':
|
|
order.status = 'completed'
|
|
order.updated_at = datetime.now()
|
|
order.save()
|
|
|
|
# 更新用户积分
|
|
user = order.user
|
|
user.points += order.plan.credits_per_month
|
|
user.save()
|
|
|
|
print(f"订单 {parent_payment} 状态更新为 'completed' 并已更新用户积分")
|
|
else:
|
|
print(f"支付未完成,状态: {payment_details['state']}")
|
|
else:
|
|
print(f"查询支付状态失败: {payment_details_response.text}")
|
|
return JsonResponse({"code": 500, "message": "查询支付状态失败", "data": {}})
|
|
|
|
except Order.DoesNotExist:
|
|
print(f"未找到订单 {parent_payment}")
|
|
return JsonResponse({"code": 400, "message": "订单不存在", "data": {}})
|
|
else:
|
|
print("Webhook payload 中缺少 parent_payment")
|
|
return JsonResponse({"code": 400, "message": "Webhook payload 中缺少 parent_payment", "data": {}})
|
|
|
|
elif event_type == 'PAYMENT.SALE.DENIED':
|
|
sale_id = payload['resource'].get('id')
|
|
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()
|
|
print(f"订单 {parent_payment} 状态更新为 'failed'")
|
|
except Order.DoesNotExist:
|
|
print(f"未找到订单 {parent_payment}")
|
|
return JsonResponse({"code": 400, "message": "订单不存在", "data": {}})
|
|
else:
|
|
print("Webhook payload 中缺少 parent_payment")
|
|
return JsonResponse({"code": 400, "message": "Webhook payload 中缺少 parent_payment", "data": {}})
|
|
|
|
return JsonResponse({"code": 200, "message": "Webhook 处理成功", "data": {}})
|
|
|
|
except json.JSONDecodeError:
|
|
print("请求体不是有效的JSON")
|
|
return JsonResponse({"code": 400, "message": "请求体不是有效的JSON", "data": {}})
|
|
except Exception as e:
|
|
print(f"处理 PayPal Webhook 时发生错误: {e}")
|
|
return JsonResponse({"code": 500, "message": f"处理 PayPal Webhook 时发生错误: {str(e)}", "data": {}})
|