124 lines
4.8 KiB
Python
124 lines
4.8 KiB
Python
import logging
|
|
import stripe
|
|
from typing import Optional, Dict, Any
|
|
from app.config import settings
|
|
from app.services.payment_gateway import PaymentGateway
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class StripePaymentService(PaymentGateway):
|
|
name = "stripe"
|
|
supported_types = ["card", "alipay", "wechat"]
|
|
|
|
def __init__(self):
|
|
self.secret_key = settings.STRIPE_SECRET_KEY
|
|
self.webhook_secret = settings.STRIPE_WEBHOOK_SECRET
|
|
if self.secret_key:
|
|
stripe.api_key = self.secret_key
|
|
|
|
async def create_order(self, order_no: str, amount: int, description: str,
|
|
**kwargs) -> Dict[str, Any]:
|
|
if not self.secret_key:
|
|
raise ValueError("Stripe 未配置")
|
|
|
|
pay_type = kwargs.get("pay_type", "card")
|
|
success_url = kwargs.get("success_url", "")
|
|
cancel_url = kwargs.get("cancel_url", "")
|
|
|
|
payment_method_types = ["card"]
|
|
if pay_type == "alipay":
|
|
payment_method_types = ["alipay"]
|
|
elif pay_type == "wechat":
|
|
payment_method_types = ["wechat_pay"]
|
|
|
|
session = stripe.checkout.Session.create(
|
|
payment_method_types=payment_method_types,
|
|
line_items=[{
|
|
"price_data": {
|
|
"currency": "usd",
|
|
"product_data": {"name": description},
|
|
"unit_amount": amount,
|
|
},
|
|
"quantity": 1,
|
|
}],
|
|
mode="payment",
|
|
success_url=success_url or "https://trade.yuzhiran.com/workspace/credits?stripe=success",
|
|
cancel_url=cancel_url or "https://trade.yuzhiran.com/workspace/credits?stripe=cancel",
|
|
metadata={"order_no": order_no},
|
|
)
|
|
|
|
return {
|
|
"gateway_order_id": session.id,
|
|
"merchant_order_id": order_no,
|
|
"session_url": session.url,
|
|
"session_id": session.id,
|
|
"amount": amount / 100,
|
|
"status": "pending",
|
|
}
|
|
|
|
async def query_order(self, order_no: str) -> Dict[str, Any]:
|
|
try:
|
|
session = stripe.checkout.Session.retrieve(order_no)
|
|
return {
|
|
"status": session.status,
|
|
"payment_status": session.payment_status,
|
|
"amount": session.amount_total / 100 if session.amount_total else 0,
|
|
"currency": session.currency or "usd",
|
|
"customer_email": session.customer_details.email if session.customer_details else None,
|
|
}
|
|
except stripe.error.StripeError as e:
|
|
logger.error(f"Stripe query failed: {e}")
|
|
return {"status": "unknown"}
|
|
|
|
async def refund(self, order_no: str, amount: int, reason: str = "") -> Dict[str, Any]:
|
|
try:
|
|
payment_intents = stripe.checkout.Session.list(
|
|
payment_intent=True
|
|
)
|
|
refund = stripe.Refund.create(
|
|
payment_intent=order_no,
|
|
amount=amount,
|
|
reason="requested_by_customer",
|
|
)
|
|
return {"status": refund.status, "refund_id": refund.id}
|
|
except stripe.error.StripeError as e:
|
|
logger.error(f"Stripe refund failed: {e}")
|
|
raise ValueError(f"退款失败: {e}")
|
|
|
|
async def query_refund(self, order_no: str) -> Dict[str, Any]:
|
|
return {"status": "not_implemented"}
|
|
|
|
def verify_callback(self, headers: dict, body: str) -> bool:
|
|
if not self.webhook_secret:
|
|
logger.warning("Stripe webhook secret not configured")
|
|
return False
|
|
try:
|
|
sig_header = headers.get("stripe-signature", "")
|
|
stripe.Webhook.construct_event(body, sig_header, self.webhook_secret)
|
|
return True
|
|
except stripe.error.SignatureVerificationError as e:
|
|
logger.warning(f"Stripe webhook signature invalid: {e}")
|
|
return False
|
|
|
|
def parse_callback(self, body: str, headers: dict) -> Dict[str, Any]:
|
|
event = stripe.Webhook.construct_event(body, headers.get("stripe-signature", ""), self.webhook_secret)
|
|
session = event.data.object
|
|
return {
|
|
"event": event.type,
|
|
"order_no": session.get("metadata", {}).get("order_no", ""),
|
|
"gateway_order_id": session.get("id", ""),
|
|
"gateway_order_no": session.get("payment_intent", ""),
|
|
"amount": (session.get("amount_total", 0) or 0) / 100,
|
|
"success": event.type == "checkout.session.completed",
|
|
"raw": session,
|
|
}
|
|
|
|
async def close_order(self, order_no: str) -> Dict[str, Any]:
|
|
try:
|
|
stripe.checkout.Session.expire(order_no)
|
|
return {"status": "expired"}
|
|
except stripe.error.StripeError as e:
|
|
logger.error(f"Stripe close order failed: {e}")
|
|
raise ValueError(f"关闭订单失败: {e}")
|