【问题标题】:How to make a rigid PayPal REST API Payment cycle?如何制定严格的 PayPal REST API 支付周期?
【发布时间】:2014-10-09 12:06:54
【问题描述】:

我在我的 Flask 应用程序上摆弄 paypalrestsdk,这是付款的顺序。有经验的人能指出这个周期是否是刚性的吗?

  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,所以定位相关支付执行的唯一方法是使用当前用户 id 和 status='created' 对 Payment 表进行盲查询。

还有改进的余地吗?

【问题讨论】:

    标签: paypal flask paypal-rest-sdk


    【解决方案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')
    

    欢迎批评。

    【讨论】:

    • 如果这回答了问题,则接受您自己的回答,以便将其标记为已关闭;如果您提供代码,我会 +1,这样答案更容易理解
    猜你喜欢
    • 2013-09-06
    • 2016-04-04
    • 2013-05-29
    • 2015-02-09
    • 2014-10-26
    • 2017-02-16
    • 2018-09-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多