Integrate Stripe payment with Ionic 4 and Django

DAY 6 - September 2019

In this tutorial we will learn how to set up Stripe on a Ionic application to accept one-time payment with Stripe but also how to save credit card details to manage reccuring payments.

Integrate one-time payment with Stripe in our Ionic application

One time payment means that we will ask our user to enter his credit card detail each time that we want him to pay.
Let's deal with the installation.
First thing, we will add in our Django project requirements.txt the stripe sdk

django>=2.0.11
psycopg2==2.7 --no-binary psycopg2
pillow==5.1.0
django-jet
djangorestframework
django-filter
git+https://github.com/mago960806/django-rest-framework-docs.git
django-oauth-toolkit
django-cors-middleware
sendgrid
stripe

In our Ionic application, we will add the stripe javascript library in our src/index.html file.

 <script src="https://js.stripe.com/v3/" async></script>

Next we will add our PaymentPage to ionic.

ionic g page PaymentPage

And as always we will move the new created directory PaymentPage inside our pages directory.
To be able to use Stripe you will need to create an account (obviously), and then to obtain a stripe publishable test key such as : pk_test_vgNZGTXEF76HVvSJF5Q7Z8UW.
This key will be use to test Stripe in our application. When your application will be ready to go on production, don't forget to replace the Stripe test key by the Stripe live key.

It's time to implement the Stripe payment in our ionic application.

import { Component, OnInit } from '@angular/core';
import { ApiDjangoService } from 'src/app/services/api-django.service';
declare var Stripe;
@Component({
  selector: 'app-payment-page',
  templateUrl: './payment-page.page.html',
  styleUrls: ['./payment-page.page.scss'],
})
export class PaymentPagePage implements OnInit {
  stripe = Stripe(""); //TO Put stripe test key or production key
  card: any;
  paymentIntent: any;
  form = { firstName: '', lastName: "", email: '', address: '', zipCode: "" };
  submitDone = false;
  priceToPay = 10; //10$
  constructor(public apiService: ApiDjangoService) {
  }
  ngOnInit() {
  }
  ionViewWillEnter() {
    console.log("on enter")
    if (this.apiService.networkConnected) {
      let price = this.priceToPay * 100;
      this.apiService.getPaymentIntent(price).subscribe((results) => {
        console.log(JSON.stringify(results))
        this.paymentIntent = results;
        this.setupStripe()
      })
    }
    else {
      this.apiService.showNoNetwork()
    }
  }
  ionViewDidEnter() {
    this.setupStripe();
  }

First we declare our Stripe module which will coming from the javascript library that we included in our index.html

declare var Stripe;

Then we need to setup a stripe variable with the test key that we obtain in our Stripe dashboard account

stripe = Stripe("pk_test_vgNZGTXEF76HVvSJF5Q7Z8UW"); 

Then we will declare variables that we will use in a form to ask our user some details such as his firstName, lastName, email...
We also harcoded the price that he will pay (10$).

In our ionViewWillEnter method, we multiply the price to pay per 100 because with Stripe we need to express the amout to pay in cents.

To be able to create a payment with Stripe, we need to create a payment intent from our backend. To create a payment intent, you just need the amout that the user will have to pay and as results, you will receive a token. This token will need to be pass to the Stripe client method after the user enters his credit card detail and submit the form to pay.
So we call our api to get an intent and then we can setup stripe.

this.apiService.getPaymentIntent(price).subscribe((results) => {
        console.log(JSON.stringify(results))
        this.paymentIntent = results;
        this.setupStripe()
      })

We need to add our new getPaymentIntent(price) to our Ionic api.

getPaymentIntent(amount){ 
  console.log("On envoi "+JSON.stringify(amount)) 
  const options = {
    headers: new HttpHeaders({
      'content-type': 'application/json',
      'Authorization': 'Bearer '+this.tokenSSO
    })
  };
  let params = {
    "amount":amount
  } 
  return Observable.create(observer => {
    // At this point make a request to your backend to make a real check!
    this.http.post(this.getPaymentIntentUrl,params,options)
     .pipe( retry(1) )
    .subscribe(res => {
      console.log("reponse paymentintent "+JSON.stringify(res))
      observer.next(res);
      observer.complete();
    }, error => {
      console.log(error);// Error getting the data
      observer.next();
      observer.complete();
    });
  });
}

and declare the getPaymentIntentUrl

getPaymentIntentUrl = this.virtualHostName + this.apiPrefix +  "/createpaymentintent/";

Ok before continuing the Ionic implementation of our payment with Stripe, we will first modify our Django bikebackend project, to add this new createpaymentintent webservice.

We will add the url in our urls.py file of our api folder.

from django.urls import path
from django.conf.urls import include,url
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
from api.views import *
urlpatterns = [
    path('users/', UserListCreateView.as_view(), name='users_list'),
   # url(r'^user/(?P<pk>[0-9A-Fa-f-]+)/$', UserDetailView.as_view(), name='user_detail'),
    path('user/<uuid:pk>/',  UserDetailView.as_view(), name='user_detail'),
    path('', include(router.urls)),
    url(r'^createpaymentintent/$',createpaymentintent,name='createpaymentintent'),
]

We declare the method createpaymentintent. Then in our views.py we add the method

@api_view(['POST'])
def createpaymentintent(request):
    if request.method == "POST":
        amount =  request.data["amount"]
        stripe.api_key = "yourapikey"
        intent = stripe.PaymentIntent.create(
            amount=int(amount),
            currency='eur',
        )
        return JsonResponse(intent, safe=False)
    else:
        return HttpResponse(status=501)

This time we don't use Django Rest framework but instead standard Django methods.
We check if the method is called with a POST request otherwise we return an http 501 response code.
Then we get the amount to pay, we set the Stripe api key (this key is for our backend server and can be obtain in our Stripe dashboard interface).
And using the Stripe python sdk, we ask for a payment intent (be careful my currency is in euro replace with dollar) that we can return as a JSON response to our Ionic application.

Ok now we can bo back to our Ionic application and continue the Stripe payment implementation.
Let's focus on our payment-page.page.html. The Ionic page will display a form in which our user will enter his details such as name, email... and his credit card detail. Stripe will automatically inject the form relative to the payment credit card.

<ion-header mode="ios">
  <ion-toolbar mode="ios">
      <h1>Payment</h1>
     <ion-menu-button>
       <ion-icon class="qt-menu"></ion-icon>
     </ion-menu-button>
   </ion-buttons>
 </ion-toolbar> 
</ion-header>
<ion-content>
  <form action="/" method="post" id="payment-form">
    <ion-list mode="ios" form-list no-lines no-bg>
      <ion-item text-wrap>
        <ion-label fixed>FirstName</ion-label>
        <ion-input type="text" #nom placeholder="FirstName" name="firstName"  [(ngModel)]="form.firstName"></ion-input>
      </ion-item>
      <ion-item text-wrap>
        <ion-label fixed>LastName</ion-label>
        <ion-input type="text" #prenom placeholder="LastName" name="lastName"  [(ngModel)]="form.lastName"></ion-input>
      </ion-item>
      <ion-item text-wrap>
          <ion-label fixed>Email*</ion-label>
          <ion-input type="email" #email placeholder="Email" name="email"  [(ngModel)]="form.email"></ion-input>
      </ion-item>
      <ion-item text-wrap>
          <ion-label fixed>Address*</ion-label>
          <ion-input type="text" #adresse placeholder="Address" name="address"  [(ngModel)]="form.address"></ion-input>
      </ion-item>
      <ion-item text-wrap>
          <ion-label fixed>Zip Code*</ion-label>
          <ion-input type="text" #codePostal placeholder="Zip code" name="zipCode"  [(ngModel)]="form.zipCode"></ion-input>
      </ion-item>
    </ion-list>
    <div class="form-row">
      <div id="card-element">
        <!-- a Stripe Element will be inserted here. -->
      </div>
      <!-- Used to display Element errors -->
      <div id="card-errors" role="alert"></div>
    </div>
    <ion-button  type="submit" color="success" mode="ios"  fill="solid"> 
      Pay {{priceToPay}}$
     </ion-button>
  </form> 
  <div text-center>
    <p color="primary" small>Secured payment. We will never know your credit card number</p>
  </div>
</ion-content>

In our payment-page.page.ts we will setup the Stripe form and manage the form submission (a.k.a ask for payment).


  setupStripe() {
    let elements = this.stripe.elements({ locale: "fr" });
    var style = {
      base: {
        color: '#32325d',
        lineHeight: '24px',
        fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
        fontSmoothing: 'antialiased',
        fontSize: '16px',
        '::placeholder': {
          color: '#aab7c4'
        }
      },
      invalid: {
        color: '#fa755a',
        iconColor: '#fa755a'
      }
    };
    this.card = elements.create('card', { hidePostalCode: true, style: style });
    this.card.mount('#card-element');
    this.card.addEventListener('change', event => {
      var displayError = document.getElementById('card-errors');
      if (event.error) {
        displayError.textContent = event.error.message;
      } else {
        displayError.textContent = '';
      }
    });
    var form = document.getElementById('payment-form');
    if (form) {
      form.addEventListener('submit', event => {
        event.preventDefault();
        if (this.form.firstName.length <= 0) {
          this.apiService.showError("Please enter your firstName")
          return
        }
        if (this.form.email.length <= 0) {
          this.apiService.showError("Please enter your email")
          return
        }
        if (this.form.address.length <= 0) {
          this.apiService.showError("Please enter your address")
          return
        }
        if (this.form.zipCode.length <= 0) {
          this.apiService.showError("Please enter your zip code")
          return
        }
        if (!this.submitDone) {
          this.submitDone = true
          let client_secret = this.paymentIntent["client_secret"]
          this.apiService.showLoading()
          this.stripe.handleCardPayment(
            client_secret,
            this.card,
            {
              payment_method_data: {
                billing_details: {
                  address: {
                    line1: this.form.address,
                    postal_code: this.form.zipCode
                    //country: this.form.pays
                  },
                  email: this.form.email,
                  name: this.form.firstName + " " + this.form.lastName
                },
              },
              receipt_email: this.form.email
            }
          ).then((result) => {
            // Handle result.error or result.paymentIntent
            console.log(JSON.stringify(result))
            if (result.error) {
              this.apiService.stopLoading()
              var errorElement = document.getElementById('card-errors');
              errorElement.textContent = result.error.message;
            }
            else {
              //Payment has been done, do whatever you want such as sending payment information to the backend
            }
          });
        }
      });
    }
  }

Once the Stripe form is setup as i said it will be injected in our html page. If the user submits the form, we will be notified in our code because of an event listener that we set on the form:

var form = document.getElementById('payment-form');
    if (form) {
      form.addEventListener('submit', event => {

When receiving the event, we check if mandatory fields have been filled (our own fields with email...) and then we check if this method has not already been called (i don't know why but the event is called twice so i used a boolean to avoid asking payment twice).

So if this is the first time that the method is called, we set our submitDone variable to true to avoid duplicate calls, and then we can get the payment intent that we received from our call to our Django backend.

let client_secret = this.paymentIntent["client_secret"]

With this token we can know ask Strip to do the payment.

this.stripe.handleCardPayment(
            client_secret,
            this.card,
            {
              payment_method_data: {
                billing_details: {
                  address: {
                    line1: this.form.address,
                    postal_code: this.form.zipCode
                    //country: this.form.pays
                  },
                  email: this.form.email,
                  name: this.form.firstName + " " + this.form.lastName
                },
              },
              receipt_email: this.form.email
            }
          ).then((result) => {
            // Handle result.error or result.paymentIntent
            console.log(JSON.stringify(result))
            if (result.error) {
              this.apiService.stopLoading()
              var errorElement = document.getElementById('card-errors');
              errorElement.textContent = result.error.message;
            }
            else {
              //Payment has been done, do whatever you want such as sending payment information to the backend
            }
          });

We passed the billing details of our own form to Stripe. If something wrong happened, Stripe will respond with an error that will be display to the user in our html page, otherwise if it's ok, you will receive the payment detail in the result variable. And that's it. Payment has been maded successfully.

May be it will be a good idea to track each payment in a dedicated table in your backend, and to do so you can send details thru dedicated Django API Rest. At this stage of all my tutorials, you should be able to do it.

Of course to test the payment, Stripe provides some fake test cards.

That's it for accepting one time payment with Stripe on your Ionic application. Just don't forget to use production keys (for Ionic and for backend) while going live.

Recurring payment with Stripe in our Ionic application

Recurring payment means that we will ask our user to enter his credit card detail and then we will save these credit card details to be able to charge the user later in the application without the need for the user to enter these details again. He will just need to confirm that he wants to pay.

You can find the source code HERE

Now we are going to see how to manage recurring payment with stripe and ionic, in next part.