Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Paypal integration to Flask application

I am slightly misunderstand Paypal flow event after reading https://developer.paypal.com/docs/api/. I'd like to integrate express checkout and credit card payments to my site. I am using Flask and paypalrestsdk without any Flask extensions.

Here is excerpts from my app:

@app.route('/', methods=['GET'])
def index():
    # Page with but form, price/quantity/name values
    # are stored in hidden fields, "Buy now" acts as submit
    return render_template('index.html')

@app.route('/payment/paypal', methods=['POST'])
def payment_paypal():
    # Here I am creating dict with required params
    payment_template = {
        'intent': 'sale',
        'payer': {'payment_method': 'paypal'},
        'redirect_urls': {
          'return_url': url_for('payment_paypal_execute'),
          'cancel_url': url_for('payment_paypal_error')
        },
        ......
    }

    payment = paypalrestsdk.Payment(payment)

    if payment.create():
        print('Payment "{}" created successfully'.format(payment.id))

        for link in payment.links:
            if link.method == "REDIRECT":
                redirect_url = str(link.href)
                print('Redirect for approval: {}'.format(redirect_url))
                return redirect(redirect_urls)

@app.route('/payment/paypal/execute', methods=['GET'])
def payment_paypal_execute():
    payer_id = request.args.get('payerId')
    payment_id = request.args.get('paymentId')
    token = request.args.get('token')

    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 as ex:
        print('Paypal resource not found: {}'.format(ex))
        abort(404)

    if payment.execute({"payer_id": payer_id}):
        pending_payment.state = payment.state
        pending_payment.updated_at = datetime.strptime(payment.update_time, "%Y-%m-%dT%H:%M:%SZ")
        db.session.commit()
        return render_template('payment/success.html', payment_id=payment.id, state=payment.state)

    return render_template('payment/error.html', payment_error=payment.error, step='Finallizing payment')

It is works fine, after clicking on button payment created succesfully (with state created) user redirected to approval page. There he click "Confirm"... And I never returned to my application, event when I specifying return_url! I.e. application could never be informed that buyer approved payment and it should be updated in my own database and new license should be sent to that person.

Problems:

  1. I cannot find way to define some callback using pyhtonrestsdk. How to do it?

  2. Even if I adding callback (I tried embed Express Checkout using pure Javascript button code) with data-callback my application was not called. I suspect because remote server could not call http://127.0.0.1/payment/paypal/success

  3. User could close window with PayPal confirmation immediately after click "Confirm" so I could not trust browser redirection it it performed somehow later.

Finally, I suspect that I do not understand PayPal workflow clear, but I could not find more information about it event on developers portal.

like image 243
Alex G.P. Avatar asked Dec 16 '15 23:12

Alex G.P.


1 Answers

As usual, devil hides in details. My main issue was following: paypal does not redirects me to my application, but I found that it redirects me (after confirmation) to URL which looks like https://sandbox.paypal.com/ with query string contains desired parameters. I.e. redirect_urls works as expected, just redirects me to wrong host.

After that I remembered that url_for generate relative links. So just added keyword _external=True I've been redirected to my application with all required arguments and payment successfully confirmed and executed.

I.e. correct redirect_urls block will looks like:

'redirect_urls': {
    'return_url': url_for('payment_paypal_execute', _external=True),
    'cancel_url': url_for('payment_paypal_error', _external=True)
}

Finally I've got following workflow:

  1. Opened / (index) which has button Pay with PayPal It is image button inside form. Beside this button form contains hidden fields with amount, product name and quantity (actually if is not good idea because we cannot trust to user, so I storing only product_license_type_id which stored in DB and contains all required information about product).

  2. Once clicked it POST form to '/payment/paypal' (paypal_create) where create object Payment with filling all fields. If call payment.create finished successfully it also creates record in my own database with payment_id and state (these fields related to paypal workflow, of course actually I am storing couple of other fields related to my app).

  3. Once payment created on PayPal side, application look into response for list payment.links. We want one with rel == 'approval_url' and method == 'REDIRECT' and return flask.redirect(found_link)

  4. On PayPal site buyer should click 'Approve', review shipping address and after that he will be immediately redirected to redirect_urls.return_url with following parameters in query string: PayerID, paymentId, token.

  5. Once redirected back you should get this parameters from query string (keep in mind - it is case-sensitive!), find payment using PayPal API (payment = paypalrestsdk.Payment.find(payment_id)) and finalize it (payment.execute({"payer_id": payer_id}):).

  6. When finalized payment changes status to approved.

  7. ....

  8. PROFIT!

UPD: You do not need to turn on "AutoRedirect" in you selling account preferences and this approach suitable for integrating one account into multiple sites.

like image 182
Alex G.P. Avatar answered Nov 18 '22 13:11

Alex G.P.