Skip to content

Instantly share code, notes, and snippets.

@avidas
Last active August 29, 2015 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save avidas/ffd3ffa7b65bef795cdc to your computer and use it in GitHub Desktop.
Save avidas/ffd3ffa7b65bef795cdc to your computer and use it in GitHub Desktop.

Build a subscription app with PayPal Python sdk, Flask, and Heroku

With the release of the subscription (recurring payment) APIs from PayPal, it is possible to create your anything-as-a-service buisness faster than ever. SDK integrations provide a native way to integrate with the PayPal APIs in the language of your choice. In this tutorial, we will walk through a sample application built using the PayPal Python sdk, the microframework Flask, Bootstrap for UI and Heroku for deployment.

Setting up

If you have not already, get your credentials from PayPal Developer Portal and see the Subscription api docs. Afterwards, set up the Python sdk. It may also be worth checking out Flask to see how to set up a simple web application (hello world is only 7 lines long!).

Merchant views

As a merchant/developer, you most common use cases may be view the billing plans you have created already. For example, to view all created plans in descending order you could do

# Result is a Json object with an array of plan objects 
plans_created = BillingPlan.all({"status": "CREATED", "sort_order": "DESC"})

To display the plans in a html page, you can use your preferred templating engine, here we use Jinja. We are using bootstrap frameworks grid system, by using the col-lg-7 tag to ensure that the columns line up properly on the html list.

{% for plan in plans_created %}
  <li class="list-group-item">
    <div class="row">
      <div class="col-lg-3 col-md-3 text-center">
      </div>
      <div class="col-lg-7 col-md-7 section-box">
        <h2>{{ plan.name }}</h2>
        <p>{{ plan.description }}</p>
      </div>
      <div class="col-lg-2 col-md-2 section-box">
      <form action="{{ url_for('activate', id=plan.id) }}" method="post">
        <input type="hidden" id="activate" name="activate" value="activate" />
        <button class="btn btn-success btn-md" type="submit"><span class="glyphicon glyphicon-cloud-upload"></span> Activate</button>
      </form>
      </div>
    </div>
  </li>
{% endfor %}

You can set up your necessary parameters for creating a Billing Plan. After creating, you may want to change to status of the plan to Active so that a customer can subscribe for the plan. In the app, the activate button sends a post request to the activate endpoint, appending the plan id as a parameter.

      <form action="{{ url_for('activate', id=plan.id) }}" method="post">
        <input type="hidden" id="activate" name="activate" value="activate" />
        <button class="btn btn-success btn-md" type="submit"><span class="glyphicon glyphicon-cloud-upload"></span> Activate</button>
      </form>

The http post call made on the activate endpoint is handled by the activate method which calls billing_plan.replace with state set to ACTIVE.

@app.route("/activate", methods=['POST'])
def activate():
    if session.get('logged_in') and session.get('merchant'):
        billing_plan_update_attributes = [
            {
                "op": "replace",
                "path": "/",
                "value": {
                    "state": "ACTIVE"
                }
            }
        ]
        billing_plan = BillingPlan.find(request.args.get('id', ''))
        print("Billing Plan state is %s " % (billing_plan.state))
        if billing_plan.replace(billing_plan_update_attributes):
            billing_plan = BillingPlan.find(request.args.get('id', ''))
            print("Billing Plan state is %s " % (billing_plan.state))
        else:
            print billing_plan.error
        return redirect(url_for('admin'))
    else:
        return redirect(url_for('login'))

Customer views

Now your users can subscribe for the plan. In this app, on login the user sees the active plans on login with a form button that makes a post to the subscribe endpoint of the application with the plan id as parameter.

  <form action="{{ url_for('subscribe', id=plan.id) }}" method="post">
    <input type="hidden" id="subscribe" name="subscribe" value="subscribe" />
    <button class="btn btn-success btn-md" type="submit"><span class="glyphicon glyphicon-shopping-cart"></span> Subscribe</button>
  </form>

Billing Agreements

The subscribe endpoint introduces the second key component of subscription apis, the Billing Agreement. The Billing Plans act as a template for the Billing Agreements which you can have users subscribe to with payment method, shipping address and other customer specific information.

We instantiate the BillingAgreement class with the necessary attributes. Afterwards we call create() on billing_agreement to create the agreement. In this example, the payment_method is paypal.

The next step of the process is to redirect the user to paypal for approving the payment. The billing_agreement created returns a list of HATEOS links where the link with attribute rel set to approval_url is the url you want to redirect the user to.

@app.route("/subscribe", methods=['POST'])
def subscribe():
    if session.get('logged_in') and session.get('customer'):
        billing_agreement = BillingAgreement({
            "description": "Agreement for organization plan",
            "start_date": "2015-02-19T00:37:04Z",
            "plan": {
                "id": request.args.get('id', '')
            },
            "payer": {
                "payment_method": "paypal"
            },
            "shipping_address": {
                "line1": "StayBr111idge Suites",
                "line2": "Cro12ok Street",
                "city": "San Jose",
                "state": "CA",
                "postal_code": "95112",
                "country_code": "US"
            }
        })
        if billing_agreement.create():
            print("Billing Agreement created successfully")
            for link in billing_agreement.links:
                if link.rel == "approval_url":
                    approval_url = link.href
                    return redirect(approval_url)
                else:
                    print(billing_agreement.error)
        return redirect(url_for('subscriptions'))
    else:
        return redirect(url_for('login'))

After the user has approved the subscription, they would then the redirected to the return_url you set up while creating the billing plan. For this application, that is the execute endpoint of the application. PayPal appends a payment token to your return_url as a parameter which you can pass into the BillingAgreement.execute method of the sdk to execute the agreement.

@app.route("/execute")
def execute():
    payment_token = request.args.get('token', '')
    billing_agreement_response = BillingAgreement.execute(payment_token)
    print("BillingAgreement[%s] executed successfully" % (billing_agreement_response.id))
    return redirect(url_for('agreement_details', id=billing_agreement_response.id))

That is the full flow of creating a billing plan, activating and having a user subscribe to it! You may want to display the details of the created plan to the user, done via calling the find method of the BillingAgreement class. A very common use case would be for a user to see their payment history which can be done via calling search_transactions on the agreement and passing in the start_date and end_date as requested.

billing_agreement = BillingAgreement.find(request.args.get('id', ''))
transactions = billing_agreement.search_transactions(start_date, end_date)

To view what else is possible with billing agreements, check out the API specs.

Creating an agreement with credit_card as the payment method is also possible similar to creating a payment.

Where to find on github and where to find on heroku

When your next killer app needs a

Future

We have conveniently used PayPal as our backend here We use PayPal as the backend and no database is required

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment