0

I'm attempting to create a stackblitz demo of Stripe elements integration. This is the demo..

I have Stripe elements declared like this:

<!DOCTYPE html>
<html lang="en">
<body>
  <script src="https://js.stripe.com/v3/"></script>
  <script type="text/javascript">
    var stripe = Stripe('pk_test_2syov9fTMRwOxYG97ABSFGSDAXbOgt008X6NL46o');t
    var elements = stripe.elements();
  </script>
  <my-app></my-app>
</body>
</html>

However it looks as if elements is not being picked up or seen as a global element by the app.component.ts file.

Any idea how to fix this?

Ole
  • 41,793
  • 59
  • 191
  • 359

2 Answers2

1

Using type script you should instead use the npm package to use it inside the components. https://stackoverflow.com/a/49078578/8161471

The way you have done it on the example it's just adding it to the global window DOM element and pretty much going against the way Angular framework works. You could technically access it from using these variables window.stripe and window.elements but I would not recommend it. Best would be to follow the guides provided by Stripe Documentation.

https://github.com/stripe/stripe-node/

Alejandro
  • 413
  • 3
  • 9
  • Hi - I tried the approach but now I get the error that `http` and `https` packages cannot be found. This is the updated stackblitz: https://stackblitz.com/edit/angular-stripe-integration?file=src%2Fapp%2Fapp.component.ts – Ole Nov 28 '19 at 02:08
  • I think this won't work for you because that library is for node. Luckily, somebody already created a tutorial on how to do it the way you were trying to do it before without importing that library. - https://alligator.io/angular/stripe-elements/ create the types and then use in your components – Alejandro Nov 28 '19 at 02:56
  • Yes I have that setup in the demo - but that's also not working ... That's actually the tutorial I was following :) – Ole Nov 28 '19 at 05:41
0

The problem is that Angular can load the component at the same time that the Stripe API is loading. This results in a race condition.

If the Stripe API is loaded via the script tag then the component can use it. If it is not then the component will not render right, because the call to stripe.elements() errors.

To solve this we have to listen for the script to complete and then use the stripe API.

This is a service that does this. You inject the service and then call:


  setPublishableKey(key:string, options?:any):Promise<Stripe>{
    return this.stripePromise.then( () => {
      return this.stripe(key, options)
    })
  }

This is the entire service:

import { Injectable } from '@angular/core';
import { Stripe, StripeFactory } from './types';

const STRIPE_API_URL = "https://js.stripe.com/v3/";

@Injectable({
  providedIn: 'root'
})
/**
 * This service has a `stripe` property to that gets
 * initialized to `window["Stripe"]`.
 * 
 * The constructor calls `inject()` which will
 * inject a script tag with containing the URL that loads
 * stripe and return a `Promise<StripeFactory>`.
 * 
 * The script tag will only load stripe if 
 * c is not available.
 * 
 * If `window["Stripe"]` is available then `inject()` resolves 
 * the promise with that instance immediately, and does not create and 
 * wait for the script tag to load.
 * 
 *  
 */
export class AngularStripeService{

  private _stripe:StripeFactory = window["Stripe"]
  private stripePromise:Promise<any>

  constructor() { 
    this.stripePromise = this.inject()
  }

  get stripe() {
    return this._stripe;
  } 
  set stripe(s:StripeFactory) {
    this._stripe = s;
  }

  setPublishableKey(key:string, options?:any):Promise<Stripe>{
    return this.stripePromise.then( () => {
      return this.stripe(key, options)
    })
  }

  inject():Promise<StripeFactory>{

    if( this.stripe ){
      return Promise.resolve( this.stripe )
    }

    return new Promise((res,rej)=>{
      const head = this.getHeadElement()
      const script = document.createElement("script")
      script.setAttribute("type", "text/javascript")
      script.setAttribute("src", STRIPE_API_URL)      
      head.appendChild(script)      
      script.addEventListener("load",()=>{
        this.stripe = window["Stripe"];
        res( this.stripe )
      })
    })
  }

  /**
   * Returns the `head` element.
   * @throws Error('Application does not have a head element');
   */
  getHeadElement(){
    let elm:HTMLElement = document.getElementsByTagName("head")[0]

    if(!elm) {
      throw new Error('Application does not have a head element');
    }    
    return elm
  }  
}

I'll be publishing it and more to NPM under the namespace @fireflysemantics/angular-stripe-service.

It will live in this repository:

https://github.com/fireflysemantics/angular-stripe-service

And a demo of the service:

https://stackblitz.com/edit/angular-stripe-integration?file=src%2Findex.html

Ole
  • 41,793
  • 59
  • 191
  • 359