Reputation: 46950
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?
Upvotes: 0
Views: 773
Reputation: 46950
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
Upvotes: 0
Reputation: 413
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/
Upvotes: 1