尝试将 Stripe webhook 与 Firebase 集成时出现空元数据错误

问题描述 投票:0回答:1
payment_intent.created [evt_3PZzGeKq4N4uKyHCf17LIwBl4]
[200] POST http://localhost:8080/api/webhook [evt_3PZzGeKq7N4uKyHCf17LIwBl4]
customer.created [evt_1PZzGsK5qN4uKyHCf83dnIhdt]
payment_intent.succeeded [evt_3PZzGeKqN54uKyHCf1D1aPjW1]
checkout.session.completed [evt_1PZzGsKqN4uKyHCf3a4IyRTBU]
[200] POST http://localhost:8080/api/webhook [evt_1PZzGsKq8N54uKyHCf83dnIhdt]
charge.succeeded [evt_3PZzGeKqN4uKyHCf1GgdWGpa]
[400] POST http://localhost:8080/api/webhook [evt_1PZzGsKqN4uKy77HCf3aIyRTBU]
[200] POST http://localhost:8080/api/webhook [evt_3PZzGeKqN4uKyHCf1D1aPjW1]
[200] POST http://localhost:8080/api/webhook [evt_3PZzGeKqN49uKyHCf1GgdWGpa]

这是在控制台中运行 stripe Listen --forward-to localhost 的结果,如果您注意到有一个 [400]。

理论上查看 Firebase 日志和我上传到 Firebase 的 Flask 应用程序的调试,错误应该是,E ext-firestore-stripe- payment-handleWebhookEvents:❗️[错误]:Stripe 事件的 Webhook 处理程序 [ [ payment_intent.succeeded ] 类型的 evt_3PZruxKqN4uKyHCf4vfsd451KjmE4fW] 失败:找不到用户!更详细地检查 Stripe 面板,我看到我处理的 webhook,这导致了错误

payment_intent.succeeded

"had an empty "metadata" field, unlike this one "checkout.session.completed
' "metadata": {
        "firebaseUID": "qUcXVAN8Zd54nj7atSSzr3YUTuxc2N4KjxB3",
        "subscriptionDays": "30"
      },'
"

我认为这可能就是为什么,因为元数据是空的,所以它找不到用户,但我不知道为什么如果其他人达到这个不,看看代码。

import os
import logging
from datetime import datetime, timedelta
import pytz
from dotenv import load_dotenv
from flask import Flask, request, jsonify, g
from flask_cors import CORS
import stripe
import firebase_admin
from firebase_admin import credentials, firestore, auth

# Cargar las variables de entorno desde el archivo .env
load_dotenv()

# Configurar los registros de la aplicación
logging.basicConfig(level=logging.DEBUG)

# Inicializar Firebase Admin SDK
try:
    cred = credentials.ApplicationDefault()
    firebase_admin.initialize_app(cred, {
        'projectId': 'xas58c6dscsdcsalkcdxasd317'
    })
except Exception as e:
    logging.error(f'Error al inicializar Firebase Admin SDK: {e}')
    raise

# Configurar la clave secreta de Stripe
stripe.api_key = os.getenv('STRIPE_API_KEY')
stripe_webhook_secret = os.getenv('STRIPE_WEBHOOK_SECRET')

if not stripe.api_key or not stripe_webhook_secret:
    logging.error('Las claves de Stripe no están configuradas correctamente en el archivo .env')
    raise EnvironmentError('Faltan las claves de Stripe en el archivo .env')

# Configurar la aplicación Flask
app = Flask(__name__)
CORS(app, resources={r"/api/*": {"origins": "*"}})

# Middleware para obtener el ID de usuario autenticado en Firebase
@app.before_request
def before_request():
    g.user_id = None  # Inicializamos g.user_id como None por defecto
    try:
        # Verificamos el token de Firebase que viene en el header Authorization
        authorization_header = request.headers.get('Authorization')
        if authorization_header:
            id_token = authorization_header.split('Bearer ')[1]
            decoded_token = auth.verify_id_token(id_token)
            g.user_id = decoded_token.get('uid')
    except Exception as e:
        logging.error(f'Error al verificar el token de Firebase: {e}')

# Manejador de ruta para el webhook de Stripe
@app.route('/api/webhook', methods=['POST'])
def handle_stripe_webhook():
    payload = request.get_data(as_text=True)
    sig_header = request.headers.get('Stripe-Signature')

    if not sig_header:
        logging.error('No se encontró la cabecera de la firma')
        return jsonify({'error': 'Falta la firma'}), 400

    try:
        event = stripe.Webhook.construct_event(
            payload=payload, sig_header=sig_header, secret=stripe_webhook_secret
        )
    except (ValueError, stripe.error.SignatureVerificationError) as e:
        logging.error(f'Error al verificar el webhook: {e}')
        return jsonify({'error': 'Verificación fallida'}), 400

    logging.debug('Evento verificado: %s', event)
    db = firestore.client()

    def update_user_subscription(user_id, subscription_days):
        user_ref = db.collection('users').document(user_id)
        user_doc = user_ref.get()

        if not user_doc.exists:
            logging.error('Usuario no encontrado: %s', user_id)
            return jsonify({'error': 'Usuario no encontrado'}), 404

        trial_end_date = user_doc.get('trialEndDate')
        if not trial_end_date:
            logging.error('Fecha de finalización de la prueba no encontrada para el usuario %s', user_id)
            return jsonify({'error': 'Fecha de finalización de la prueba no encontrada'}), 400

        trial_end_datetime = trial_end_date if isinstance(trial_end_date, datetime) else trial_end_date.to_pydatetime()
        now = datetime.now(pytz.utc)
        effective_date = max(trial_end_datetime, now)
        new_end_datetime = effective_date + timedelta(days=subscription_days)
        new_end_datetime = new_end_datetime.astimezone(pytz.utc)

        user_ref.update({'trialEndDate': new_end_datetime})
        logging.debug('Suscripción actualizada correctamente para el usuario %s', user_id)
        return jsonify({'message': 'Éxito'}), 200

    def handle_checkout_session_completed(session):
        user_id = g.user_id
        metadata = session.get('metadata', {})
        subscription_days = metadata.get('subscriptionDays')

        logging.debug(f'Datos de la sesión: {session}')

        if not user_id or not subscription_days:
            logging.error('Datos de la sesión incompletos: user_id: %s, subscription_days: %s', user_id, subscription_days)
            return jsonify({'error': 'Datos de la sesión incompletos'}), 400

        try:
            subscription_days = int(subscription_days)
        except ValueError:
            logging.error('subscriptionDays no es un entero válido: %s', subscription_days)
            return jsonify({'error': 'subscriptionDays no es un entero válido'}), 400

        return update_user_subscription(user_id, subscription_days)

    try:
        if event['type'] == 'checkout.session.completed':
            return handle_checkout_session_completed(event['data']['object'])
        else:
            logging.info('Evento no manejado: %s', event['type'])
            return jsonify({'message': 'Evento no manejado'}), 200

    except Exception as e:
        logging.error('Error procesando el evento: %s', e)
        return jsonify({'error': 'Error interno del servidor'}), 500

if __name__ == '__main__':
    app.run(debug=True, port=8080)

我不太擅长Python,我认为错误一定是在我的代码中,但我对任何可能的解决方案持开放态度,顺便说一句,在API测试模式下我无论如何都会收到错误,但它会更新用户订阅,但使用生产模式下的 API 密钥即使付款正确,也不会更新订阅。

python firebase webhooks
1个回答
0
投票

在创建 Pago en Stripe 会话期间聚合必要的元数据。 Aquí tienes un ejemplo de cómo hacerlo en Python:

import stripe

stripe.api_key = 'tu_clave_secreta_de_stripe'

sesión = stripe.checkout.Session.create(
    payment_method_types=['card'],
    line_items=[{
        'price_data': {
            'currency': 'usd',
            'product_data': {
                'name': 'Nombre del producto',
            },
            'unit_amount': 2000,
        },
        'quantity': 1,
    }],
    mode='payment',
    success_url='https://tu_sitio/success?session_id={CHECKOUT_SESSION_ID}',
    cancel_url='https://tu_sitio/cancel',
    metadata={
        'firebaseUID': 'usuario123',
        'subscriptionDays': '30'
    }
)
  1. Manejar el webhook en Flask:

    从 Flask 导入 Flask、请求、jsonify 进口条纹

    应用程序 = Flask(名称) stripe.api_key = 'tu_clave_secreta_de_stripe' 端点_秘密 = 'tu_secreto_del_endpoint'

    @app.route('/webhook',methods=['POST']) def stripe_webhook(): 负载 = request.get_data(as_text=True) sig_header = request.headers.get('条纹签名')

     try:
         event = stripe.Webhook.construct_event(
             payload, sig_header, endpoint_secret
         )
     except ValueError as e:
         return 'Invalid payload', 400
     except stripe.error.SignatureVerificationError as e:
         return 'Invalid signature', 400
    
     if event['type'] == 'payment_intent.succeeded':
         payment_intent = event['data']['object']
         firebaseUID = payment_intent['metadata']['firebaseUID']
         subscriptionDays = payment_intent['metadata']['subscriptionDays']
         # Actualiza la suscripción en Firebase usando firebaseUID y subscriptionDays
    
     return jsonify(success=True)
    

    if name == 'main': 应用程序运行(端口=4242) 在此输入代码

© www.soinside.com 2019 - 2024. All rights reserved.