Как сделать цикл платежей PayPal REST API жестким?

Я возился с paypalrestsdk в своем приложении Flask, и вот последовательность оплаты. Может ли кто-нибудь из опытных указать, жесткий цикл или нет?

  1. Создайте платеж с соответствующими URL-адресами перенаправления.

    payment = paypalrestsdk.Payment({
        "intent": "sale",
        "payer": {
            "payment_method": "paypal"
        },
        "redirect_urls": {
            "return_url": url_for('.payment_success', _external=True),
            "cancel_url": url_for('.payment_cancel', _external=True)
        },
    ...})
    if payment.create():
          ...
    
  2. В случае успеха сохраните следующие данные ответа на платеж в базе данных.

    class PayPalPayment(db.Model):
        __tablename__ = 'ediket_paypal_payment'
        id = db.Column(db.Integer, primary_key=True)
        user_id = db.Column(db.Integer, db.ForeignKey('ediket_ediketuser.id'))
        payment_id = db.Column(db.String(30))
        amount = db.Column(db.String(10))
        state = db.Column(db.String(10))
        redirect_url = db.Column(db.VARCHAR)
        created_at = db.Column(db.DateTime)
        updated_at = db.Column(db.DateTime)
    
  3. Затем выполните перенаправление с использованием ссылок HATEOAS.

  4. Транзакция проходит в PayPal. Перенаправляет на возвращаемый URL.
  5. Найти последнюю транзакцию текущего пользователя со статусом «создано» из базы данных.

    pending_payment = PayPalPayment.query.filter_by(user_id=user_id).filter_by(state='created').first()
    
  6. Выполнить платеж с помощью payment_id и payer_id

    payment = paypalrestsdk.Payment.find(pending_payment.payment_id)
    if payment.execute({"payer_id": request.args.get('PayerID')}):
        #We are done!
        #Update Payment model for completion
    

Все работает нормально, но я не слишком уверен, безопасен ли этот поток для реальных приложений. Поскольку возвращаемый URL-адрес содержит только PayerID и Token, единственный способ найти соответствующий платеж для выполнения - это слепой запрос в таблице платежей с использованием текущего идентификатора пользователя и статуса = 'created'.

Есть ли место для улучшения?


person lustdante    schedule 15.08.2014    source источник


Ответы (1)


Любая модификация с тех пор заключалась в том, что я сохранил дополнительное поле с именем token, которое передается со ссылками HATEOAS. Сохранив этот токен, я смог найти ожидающий платеж пользователя, чтобы продолжить выполнение.

Обновление:

Вот детали реализации. Возможно, он не идеален, но на данный момент он сработал.

class PayPalPayment(db.Model):
    __tablename__ = 'paypal_payment'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
    payment_id = db.Column(db.String(30))
    amount = db.Column(db.String(10))
    state = db.Column(db.String(10))
    token = db.Column(db.String(30))
    redirect_url = db.Column(db.Text)
    created_at = db.Column(db.DateTime)
    updated_at = db.Column(db.DateTime)

    def __init__(self, payment, product_id, user_id):
        self.user_id = user_id
        self.product_id = product_id
        self.payment_id = payment.id
        self.amount = payment.transactions[0].amount.total
        self.state = payment.state

        self.created_at = datetime.datetime.strptime(payment.create_time, "%Y-%m-%dT%H:%M:%SZ")
        self.updated_at = datetime.datetime.strptime(payment.update_time, "%Y-%m-%dT%H:%M:%SZ")

        for link in payment.links:
            if link.method == "REDIRECT":
                self.redirect_url = link.href

        parsed = urlparse.urlparse(self.redirect_url)
        self.token = urlparse.parse_qs(parsed.query)['token'][0]

@payment_view.route('/paypal', methods=['POST'])
@login_required
def paypal_payment():
    user_id = current_user.get_id()
    product = Product.query.filter_by(id=request.form.get('product')).first_or_404()
    past = datetime.datetime.utcnow() - datetime.timedelta(minutes=3)
    count = PayPalPayment.query.filter_by(user_id=user_id).filter_by(state='created').filter(PayPalPayment.created_at >= past).count()

    # To prevent a spam where user creates Payment and never comes back to it
    if count > 4:
        return Response(render_template('payment/overflow.html'), mimetype='text/html')

    payment = paypalrestsdk.Payment({
        "intent": "sale",
        "payer": {
            "payment_method": "paypal"
        },
        "redirect_urls": {
            "return_url": url_for('.payment_success', _external=True),
            "cancel_url": url_for('.payment_cancel', _external=True)
        },

        "transactions": [{
            "amount": {
                "total": product.amount,
                "currency": "USD"
            },
            "description": product.description
        }]
    })

    if payment.create():
        new_payment = PayPalPayment(payment, product.id, user_id)
        db.session.add(new_payment)
        db.session.commit()
        return redirect(new_payment.redirect_url, code=302)
    else:
        return Response(render_template('payment/cancel.html'), mimetype='text/html')

@payment_view.route('/success')
@login_required
def payment_success():
    user_id = current_user.get_id()
    payerID = request.args.get('PayerID', None)
    token = request.args.get('token', None)

    pending_payment = PayPalPayment.query.filter_by(token=token).filter_by(state='created').first_or_404()

    try:
        payment = paypalrestsdk.Payment.find(pending_payment.payment_id)
    except paypalrestsdk.exceptions.ResourceNotFound:
        abort(404)

    template = 'payment/error.html'
    if payment.execute({"payer_id": request.args.get('PayerID')}):
        pending_payment.state = payment.state
        pending_payment.updated_at = datetime.datetime.strptime(payment.update_time, "%Y-%m-%dT%H:%M:%SZ")
        template = 'payment/success.html'

    db.session.commit()
    return Response(render_template(template), mimetype='text/html')

Пожалуйста, не стесняйтесь критиковать.

person lustdante    schedule 23.08.2014
comment
если это решает проблему, примите свой ответ, чтобы его можно было пометить как закрытое; Я бы добавил +1, если бы вы предоставили код, поэтому ответ будет легче понять - person Oliver; 28.08.2014