Aurelia Google SignIn Button - javascript

I just started using Aurelia and I am having a problem with Google Sign in. It looks like I might be able to create my own Google button but I'd rather get it to work this way if it is possible. Here is my code:
<script src="https://apis.google.com/js/platform.js" async defer></script>
...
<body aurelia-app="src/main">
...
<span id="googleButtonPlaceholder" class="g-signin2" data-onsuccess="onSignIn"></span>
I have the function setup in my Aurelia class but I do not know if/how I can call it. I have tried ${onSignIn()} which just calls the function when it loads, ${onSignIn}, onSignIn(), onSignIn, data-onsuccess.bind="onSignin()" but nothing seems to work. Is there a way to pass the Aurelia function to the Google data-onsuccess attribute?
As a note, I am switching from Angular 1.5.8 where this previously worked.

Here's an example: https://gist.run?id=5da90f48b43b9c5867c8d2ace0f6371f
app.html
<template>
<require from="google-signin-button"></require>
<google-signin-button success.call="signinSuccess(googleUser)"
error.call="signinError(error)">
</google-signin-button>
<h1>${message}</h1>
</template>
app.js
export class App {
message = 'Not signed in.';
signinSuccess(googleUser) {
const name = googleUser.getBasicProfile().getName();
this.message = `Signed in: ${name}`;
}
signinError(error) {
this.message = `Error: ${error}`;
}
}
google-signin-button.js
import {inject, noView, bindable} from 'aurelia-framework';
const googleSigninClientID = '927519533400-mfupo3lq9cjd67fmmvtth7lg7d8l50q9.apps.googleusercontent.com';
function preparePlatform() {
// https://developers.google.com/identity/sign-in/web/build-button
// The name of the global function the platform API will call when
// it's ready.
const platformCallbackName = 'setGooglePlatformReady';
// An "API ready" promise that will be resolved when the platform API
// is ready.
const ready = new Promise(
resolve => window[platformCallbackName] = resolve);
// Inject the client id meta tag
const meta = document.createElement('meta');
meta.name = 'google-signin-client_id';
meta.content = googleSigninClientID;
document.head.appendChild(meta);
// Inject an async script element to load the google platform API.
// Notice the callback name is passed as an argument on the query string.
const script = document.createElement('script');
script.src = `https://apis.google.com/js/platform.js?onload=${platformCallbackName}`;
script.async = true;
script.defer = true;
document.head.appendChild(script);
return ready;
}
const platformReady = preparePlatform();
#noView()
#inject(Element)
export class GoogleSigninButton {
#bindable success = googleUser => { };
#bindable error = error => { };
#bindable scope = 'profile email';
#bindable theme = 'dark';
#bindable width = 240;
#bindable height = 50;
constructor(element) {
this.element = element;
}
attached() {
platformReady.then(this.renderButton);
}
renderButton = () => {
gapi.signin2.render(this.element, {
scope: this.scope,
width: this.width,
height: this.height,
longtitle: true,
theme: this.theme,
onsuccess: googleUser => {
console.info(googleUser);
this.success({ googleUser });
},
onfailure: error => {
console.error(error);
this.failure({ error });
}
});
}
}

#JeremyDanyow had a great answer but after I went to bed and read a little more about Aurelia, I thought of a solution to try before seeing his answer so I thought I'd share an alternate approach for those interested.
index.html
<main aurelia-app="src/main">
</main>
<script src="https://apis.google.com/js/platform.js" async defer></script>
app.html
<template>
<span id="my-signin2"></span>
<!-- other stuff -->
</template>
app.js
attached() {
this.render();
}
render() {
gapi.signin2.render('my-signin2', {
'scope': 'profile email',
'theme': 'dark',
'onsuccess': this.onSuccess,
'onfailure': this.onFailure
});
}
onSuccess(googleuser) {
let gUser = googleuser.getBasicProfile(),
id_token = googleuser.getAuthResponse().id_token;
}
onFailure(error) {
console.log(error);
}
This approach differs slightly from what Google shows on their website where they have you give platform.js an onload function to render the button. Instead, I create the button in the template and then once the template is done being loaded, attached() is called, which in turn, calls the function I would have had platform.js call onload.

Try data-onsuccess.call="onSignIn()".

After following #JeremyDanyow's example around a few corners I came up with this
It works ok for simple usage, but needs help...
when there is another window open using a google login there is an error loading something in the iframe google adds (this doesn't seem to break it)
the listeners don't work for more than a couple of login/logouts at most
Here's hoping that someone else can improve upon this.
google-signin-button.js
import { inject, noView, bindable } from 'aurelia-framework';
import { LogManager } from 'aurelia-framework';
const Console = LogManager.getLogger('google-signin-button');
// Integrating Google Sign-In into your web app
// https://developers.google.com/identity/sign-in/web/reference
// https://console.developers.google.com/apis/credentials
// inspiration: https://developers.google.com/identity/sign-in/web/build-button
function preparePlatform(): Promise<Function> {
// Inject an async script element to load the google platform API.
const script = document.createElement('script');
script.src = `https://apis.google.com/js/platform.js?onload=gapi_ready`;
script.async = true;
script.defer = true;
document.head.appendChild(script);
// return a promise that will resolve with the onload callback
return new Promise(resolve => window['gapi_ready'] = resolve);
}
#noView
#inject(Element)
export class GoogleSigninButton {
#bindable authenticated = (signedIn: Boolean) => { };
#bindable authorized = (GoogleUser: any) => { };
#bindable scope = 'profile email';
#bindable clientId = 'none';
#bindable theme = 'dark';
#bindable width = 240;
#bindable height = 50;
public element: Element;
constructor(element) {
this.element = element;
}
public wasAuthenticated: Boolean;
sendAuthenticated(signedIn: Boolean) {
if (signedIn !== this.wasAuthenticated) {
this.authenticated(signedIn);
this.wasAuthenticated = signedIn;
}
}
public wasAuthorized: any;
sendAuthorized(googleUser: any) {
if (googleUser !== this.wasAuthorized) {
this.authorized(googleUser);
this.wasAuthorized = googleUser;
this.sendAuthenticated(true);
}
}
attached() {
// inject the script tag
preparePlatform()
.then(() => {
// load the auth lib
// Console.debug('gapi created, loading auth2');
window['gapi'].load('auth2', () => {
// init the auth lib
// Console.debug('gapi.auth2 loaded, intializing with clientId:', this.clientId);
window['gapi'].auth2.init({
client_id: this.clientId
})
.then(
(googleAuth: any) => {
// Console.debug('gapi.auth2 intialized');
// listen for user signed in/out
googleAuth.isSignedIn.listen((signedIn: Boolean) => {
// Console.debug('googleAuth.isSignedIn.listener', signedIn);
this.sendAuthenticated(signedIn);
});
// listen for who signed in
googleAuth.currentUser.listen((googleUser: any) => {
// Console.debug('googleAuth.currentUser.listener', googleUser);
this.sendAuthorized(googleUser);
});
// draw the button
window['gapi'].signin2.render(this.element, {
scope: this.scope,
width: this.width,
height: this.height,
longtitle: true,
theme: this.theme,
onsuccess: (googleUser: any) => {
// Console.debug('gapi.signin2.render success', googleUser);
this.sendAuthorized(googleUser);
},
// drawing button failure
onfailure: (error: any) => {
Console.error('gapi.signin2.render failure', error);
}
});
},
// intialization error
(errObj: any) => {
Console.error('gapi.auth2.init -> errObj', errObj);
}
);
});
});
}
}
some-usage.js
import environment from '../environment';
import { LogManager } from 'aurelia-framework';
const Console = LogManager.getLogger('Login');
import { inject } from 'aurelia-framework';
import { AuthService } from 'aurelia-authentication';
import { EventAggregator } from 'aurelia-event-aggregator';
import './login.scss';
#inject(AuthService, EventAggregator)
export class Login {
public authService: AuthService;
public eventAggregator: EventAggregator;
public googleSigninClientID: string = 'none';
constructor(authService: AuthService, eventAggregator: EventAggregator) {
this.eventAggregator = eventAggregator;
this.authService = authService;
this.googleSigninClientID = environment.googleSigninClientID;
};
isAuthenticated(signedIn: Boolean) {
Console.warn('isAuthenticated', signedIn);
}
isAuthorized(googleUser: any) {
Console.warn('isAuthorized', googleUser);
}
}
some-usage.html
<template>
<require from="../resources/elements/google-signin-button"></require>
<section>
<div class="container-fluid">
<center>
<google-signin-button client-id.bind="googleSigninClientID" authenticated.bind="isAuthenticated" authorized.bind="isAuthorized"> </google-signin-button>
</center>
</div>
</section>
</template>

Related

How will I access the components in the script inside a template?

I would like reuse my html components that contains some javascript code, so for simplify I bring one simple example:
index.html:
<!DOCTYPE html>
<html>
<head></head>
<body>
<my-component></my-component>
<script src="index.js"></script>
</body>
</html>
my-component.html:
<template>
<div id="something"></div>
<script>
// It doesn't work, this here is "window"
document.getElementById("something").innerHTML = "Something"
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, {mode: "no-cors"})
.then(res => res.text())
.then(html => {
const parser = new DOMParser()
const document = parser.parseFromString(html, 'text/html')
const head = document.head
const template = head.querySelector('template')
return template
})
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({mode: 'open'})
shadow.appendChild(document.importNode(template.content, true))
}
}
return customElements.define(name, UnityComponent)
}
function loadComponent (name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template))
}
return {loadComponent}
}())
makeComponent.loadComponent("my-component", "my-component.html")
I can with this code, but it copy all variables of the script to window:
<template>
<div id="something"></div>
<style onload="templFunc.apply(this.getRootNode())"></style>
<script>
function templFunc() {
// It works
let text = "Something"
this.querySelector('#something').innerHTML = text
// but...
console.log(window.text) // => "Something"
}
</script>
</template>
It doesn't make a sense, if the script is inside the template at least should can access the elements inside the template, else the template is almost not util for the javascript, so, I can't understand the intention of use script inside the template or how to reuse the web components that use javascript, Is it wrong do this?
So, How to I access the components in the script inside a template without copy all script variables to window?
As you found out <script> inside a <template> runs in Global scope
If you use Angular, note Angular bluntly removes all <script> content from Templates.
One workaround is to add an HTML element that triggers code within Element scope.
<img src onerror="[CODE]"> is the most likely candidate:
This then can call a Global function, or run this.getRootNode().host immediatly.
<template id=scriptContainer>
<script>
console.log("script runs in Global scope!!");
function GlobalFunction(scope, marker) {
scope = scope.getRootNode().host || scope;
console.log('run', marker, 'scope:', scope);
scope.elementMethod && scope.elementMethod();
}
</script>
<img src onerror="(()=>{
this.onerror = null;// prevent endless loop if function generates an error
GlobalFunction(this,'fromIMGonerror');
})()">
</template>
<my-element id=ONE></my-element>
<my-element id=TWO></my-element>
<script>
console.log('START SCRIPT');
customElements.define('my-element',
class extends HTMLElement {
connectedCallback() {
console.log('connectedCallback', this.id);
this.attachShadow({ mode: 'open' })
.append(scriptContainer.content.cloneNode(true));
}
});
</script>
More detailed playground, including injecting SCRIPTs, at: https://jsfiddle.net/WebComponents/q0k8ts6b/
Here is the solution,
my-component:
<template>
<div id="something"></div>
<script>
makeComponent.getComponent("my-component", "something").innerHTML = "Something"
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, { mode: "no-cors" })
.then((res) => res.text())
.then((html) => {
const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");
const head = document.head;
const template = head.querySelector("template");
return template;
});
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
this.setAttribute("id", name);
shadow.appendChild(document.importNode(template.content, true));
}
}
return customElements.define(name, UnityComponent);
}
function getComponent(host, query) {
return document.getElementById(host).shadowRoot.querySelector(query);
}
function loadComponent(name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template));
}
return { getComponent, loadComponent };
})();
makeComponent.loadComponent("my-component", "my-component.html");
However I think that this is not the better way, maybe I need use the events here, and pass the shadow scope to a listener that is called in the script tag in the template, but I don't know how to pass the scope to the event yet.
Up:
With events:
my-component:
<template>
<div id="something"></div>
<script>
document.addEventListener("custom-event", (e) => {
console.log(e.detail.target.shadowRoot.getElementById("date-year"));
})
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, { mode: "no-cors" })
.then((res) => res.text())
.then((html) => {
const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");
const head = document.head;
const template = head.querySelector("template");
return template;
});
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
shadow.appendChild(document.importNode(template.content, true));
const event = new CustomEvent("custom-event", {'detail': {
target: this
}});
document.dispatchEvent(event);
}
}
return customElements.define(name, UnityComponent);
}
function loadComponent(name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template));
}
return { loadComponent };
})();
makeComponent.loadComponent("my-component", "my-component.html");
However, I prefer the first solution even. But if you need of nested components the first doesn't work, you need of the second.

Azure Devops(vsts) expension - How to create task attachment content

I am working on creating an extension for azure devops, which creates a custom tab and displays the result.
I uploaded the file using "##vso[task.addattachment]".
Eg: console.log('##vso[task.addattachment type=TestReport;name=MyReport;]c:/user/index.html');
I am having problem in consuming that file and displaying it on new tab, I went through the sample code provided by MS - build_release_enhancer
but still unable to display the file.
js file::
import Controls = require("VSS/Controls");
import VSS_Service = require("VSS/Service");
import TFS_Build_Contracts = require("TFS/Build/Contracts");
import TFS_Build_Extension_Contracts = require("TFS/Build/ExtensionContracts");
import DT_Client = require("TFS/DistributedTask/TaskRestClient");
import { error } from "azure-pipelines-task-lib";
export class InfoTab extends Controls.BaseControl {
constructor() {
super();
}
public initialize(): void {
super.initialize();
// Get configuration that's shared between extension and the extension host
var sharedConfig: TFS_Build_Extension_Contracts.IBuildResultsViewExtensionConfig = VSS.getConfiguration();
var vsoContext = VSS.getWebContext();
if(sharedConfig) {
// register your extension with host through callback
sharedConfig.onBuildChanged((build: TFS_Build_Contracts.Build) => {
this._initBuildInfo(build);
var taskClient = DT_Client.getClient();
taskClient.getPlanAttachments(vsoContext.project.id, "build", build.orchestrationPlan.planId,"ATTACHMENT_TYPE_HERE").then((taskAttachments) => {
$.each(taskAttachments, (index, taskAttachment) => {
if (taskAttachment._links && taskAttachment._links.self && taskAttachment._links.self.href) {
var recId = taskAttachment.recordId;
var timelineId = taskAttachment.timelineId;
taskClient.getAttachmentContent(vsoContext.project.id, "build", build.orchestrationPlan.planId,timelineId,recId,"ATTACHMENT_TYPE_HERE",taskAttachment.name).then((attachementContent)=> {
function arrayBufferToString(buffer){
var arr = new Uint8Array(buffer);
var str = String.fromCharCode.apply(String, arr);
return str;
}
var data = arrayBufferToString(attachementContent);
});
}
});
});
});
}
}
private _initBuildInfo(build: TFS_Build_Contracts.Build) {
}
}
InfoTab.enhance(InfoTab, $(".build-info"), {});
// Notify the parent frame that the host has been loaded
VSS.notifyLoadSucceeded();
Html file:
<!DOCTYPE html>
<head>
<script src="../lib/VSS.SDK.min.js"></script>
<script type="text/javascript">
VSS.init( {
usePlatformScripts: true,
// moduleLoaderConfig: {
// paths: { "sample": "sample" }
// }
});
VSS.ready(function() {
require(["sample/tab2"], function () { });
});
</script>
<style>
.build-info {
padding: 10px;
}
</style>
</head>
<body>
<div class="build-info"> </div>
</body>
</html>
The issue is resolved, actually the issue was with my vss-extension.json file. I had to declare scope:
"scopes": [
"vso.build_execute"
]

Angular dynamic localisation and facebook.sdk does not update the translation for the Like button

I have added the Facebook js-sdk as a directive in an angular app, I have a button the switch the pages language my problem is that the sdk does not update the text for the Like button, even after I removed the sdk and add a new one for the selected language. I ran the FB.XFBML.parse() to update the txt but it will keep on using the first selected language only after a page refresh would changes take place
UPDATED CODE
Now Using a service with ngrx when language change it will replace the Facebook SDK for that language and dispatches an event for any the page directives to remove all the tags/classes Facebook added on the like button element and clear its children. Everything works the button show up but its still in the first language selected it does not change language
Code Service
#Injectable()
export class FacebookService {
constructor(private window: any, private document, private store: Store<fromRoot.State>) {
store.pipe(select(fromCore.getSelectedLang)).subscribe(lang => this.switchScript(lang));
if (!this.window.fbAsyncInit) {
this.window.fbAsyncInit = () => {
this.window.FB.init({
appId: environment.appId,
autoLogAppEvents: true,
cookie: true,
xfbml: true,
version: 'v4.0',
});
};
}
}
switchScript(lang: string) {
const langUrl = lang === 'ar' ? 'ar_AR' : 'en_US';
const newScript = document.createElement('script');
newScript.id = 'facebook-sdk';
newScript.async = true;
newScript.src = `https://connect.facebook.net/${langUrl}/sdk.js`;
const scriptTag = this.document.getElementById('facebook-sdk');
if (scriptTag) {
scriptTag.parentNode.replaceChild(newScript, scriptTag);
} else {
this.document.body.appendChild(newScript);
}
this.store.dispatch(SocialAction.UpdateTags({lang}));
}
}
Code Directive
#Directive({selector: '[lyxFacebook]'})
export class FacebookDirective implements AfterViewInit, OnDestroy {
tag$: Subscription;
constructor(private window: any,private document, private store: Store<fromRoot.State>) {
this.tag$ = store.pipe(select(Core.getTagLang)).subscribe(lang=> this.update(lang));
}
ngAfterViewInit(): void {
if (this.window.FB) {
this.window.FB.XFBML.parse();
}
}
update(lang: string) {
if (this.window.FB && lang) {
const tags = this.document.getElementsByClassName('fb-like');
if (tags && tags.length) {
for (let i = 0; i < tags.length; i++) {
tags[i].removeAttribute('fb-xfbml-state');
tags[i].removeAttribute('fb-iframe-plugin-query');
tags[i].classList.remove('fb_iframe_widget');
tags[i].innerHTML = '';
}
this.window.FB.XFBML.parse();
}
}
}
ngOnDestroy(): void {
if (this.tag$) {
this.tag$.unsubscribe();
}
}
}

Access Script Tag Callback from React - AmazonPay

I am in the process of integrating AmazonPay into a React SPA. The classic integration relies on script tags and callbacks (docs).
Here is one example from the button widget:
<head>
<script type='text/javascript'>
window.onAmazonLoginReady = function() {
amazon.Login.setClientId('CLIENT-ID');
};
window.onAmazonPaymentsReady = function() {
showButton();
};
</script>
<script async="async" src='https://static-na.payments-amazon.com/OffAmazonPayments/us/sandbox/js/Widgets.js'>
</script>
</head>
<body>
. . .
<div id="AmazonPayButton">
</div>
...
<script type="text/javascript">
function showButton(){
var authRequest;
OffAmazonPayments.Button("AmazonPayButton", "SELLER-ID", {
type: "TYPE",
color: "COLOR",
size: "SIZE",
authorization: function() {
loginOptions = {scope: "SCOPES",
popup: "POPUP-PARAMETER"};
authRequest = amazon.Login.authorize (loginOptions,
"REDIRECT-URL");
},
onError: function(error) {
// your error handling code.
// alert("The following error occurred: "
// + error.getErrorCode()
// + ' - ' + error.getErrorMessage());
}
});
};
</script>
. . .
<script type="text/javascript">
document.getElementById('Logout').onclick = function() {
amazon.Login.logout();
};
</script>
</body>
When using React, the div with id="AmazonPayButton" isn't on the page until React mounts the div, causing the window.showButton() function to fail.
To circumvent this issue, I've wrapped the function showButton() definition inside window.showButton():
window.onAmazonPaymentsReady = function() {
window.showButton = function () {
var authRequest;
// eslint-disable-next-line no-undef
OffAmazonPayments.Button("AmazonPayButton", "%REACT_APP_AMAZON_SELLER_ID_SANDBOX%", {
type: "PwA",
color: "Gold",
size: "medium",
authorization: function () {
loginOptions = {
scope: "profile postal_code payments:widget payments:shipping_address",
popup: true
};
authRequest = amazon.Login.authorize(loginOptions, "%PUBLIC_URL%/pay-with-amazon");
},
onError: function (error) {
console.log(error.toString())
}
});
};
};
The component which contains the AmazonPay div can now be called on componentDidMount:
import React, {Component} from 'react'
class AmazonMethod extends Component {
componentDidMount () {
window.showButton()
}
render() { return <div id="AmazonPayButton"></div>}
}
export default AmazonMethod
I am confused how to access the onError callback from inside my React component. How do I listen for the callback and respond appropriately?
This question applies to AddressWidget and WalletWidget as well; they all rely on script tag callbacks.
Update:
I've written a post which summarizes how to integrate AmazonPay with client side React.
Why don't you just pass in a function to your showButton function in componentDidMount that onError can call?
Something like this:
window.onAmazonPaymentsReady = function() {
window.showButton = function (errorFunc) {
var authRequest;
// eslint-disable-next-line no-undef
OffAmazonPayments.Button("AmazonPayButton", "%REACT_APP_AMAZON_SELLER_ID_SANDBOX%", {
type: "PwA",
color: "Gold",
size: "medium",
authorization: function () {
loginOptions = {
scope: "profile postal_code payments:widget payments:shipping_address",
popup: true
};
authRequest = amazon.Login.authorize(loginOptions, "%PUBLIC_URL%/pay-with-amazon");
},
onError: function (error) {
console.log(error.toString())
errorFunc(error)
}
});
};
};
import React, {Component} from 'react'
class AmazonMethod extends Component {
componentDidMount () {
window.showButton(this.errorFunc)
}
errorFunc = (error) => {
console.log(error);
this.setState({
amazonError: error
});
}
render() { return <div id="AmazonPayButton"></div>}
}
export default AmazonMethod

React js Stripe checkout is not working

I am trying to render a stripe checkout default form in React js application.
<form action="/your-server-side-code" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" className="stripe-button"
data-key="pk_test_oDALA0jNyxDzbRz5RstV4qOr"
data-amount="999"
data-name="test"
data-description="Widget"
data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
data-locale="auto">
</script>
</form>
Its not displaying anything and not getting error also.
How do i get that pay button and form.
The main issue you are probably having is loading a script within React.
One approach is to load the checkout script only when needed (assuming some form of spa), then just directly call it. This is akin to the "custom" version on the documentation page: https://stripe.com/docs/checkout#integration-custom
If you are already loading checkout.js (for example before your "app.js"), then the below can be simplified a bit by not manually loading in the script.
import React from 'react';
export default class Cards extends React.Component {
constructor(props:Object) {
super(props);
this.state = {
loading: true,
stripeLoading: true,
};
}
loadStripe(onload:Function) {
if(! window.StripeCheckout) {
const script = document.createElement('script');
script.onload = function () {
console.info("Stripe script loaded");
onload();
};
script.src = 'https://checkout.stripe.com/checkout.js';
document.head.appendChild(script);
} else {
onload();
}
}
componentDidMount() {
this.loadStripe(() => {
this.stripehandler = window.StripeCheckout.configure({
key: 'pk_test_xxxxxxxxxxxxxxxxxxxxxxxx',
image: 'https://stripe.com/img/documentation/checkout/marketplace.png',
locale: 'auto',
token: (token) => {
this.setState({ loading: true });
axios.post('/your-server-side-code', {
stripeToken: token.id,
});
}
});
this.setState({
stripeLoading: false
});
});
}
componentWillUnmount() {
if(this.stripehandler) {
this.stripehandler.close();
}
}
onStripeUpdate(e:Object) {
this.stripehandler.open({
name: 'test',
description: 'widget',
panelLabel: 'Update Credit Card',
allowRememberMe: false,
});
e.preventDefault();
}
render() {
const { stripeLoading, loading } = this.state;
return (
<div>
{(loading || stripeLoading)
? <p>loading..</p>
: <button onClick={this.onStripeUpdate}>Add CC</button>
}
</div>
);
}
}
Chris's answer was excellent, however I had to make a few minor changes in order for the code to function. I've also removed the TypeScript function types (for those of us not using TypeScript). Comments are added where changes to the answer have been made. FYI this is my first post, please let me know if this should be a Comment instead of an Answer.
export default class Cards extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
stripeLoading: true,
};
// onStripeUpdate must be bound or else clicking on button will produce error.
this.onStripeUpdate = this.onStripeUpdate.bind(this);
// binding loadStripe as a best practice, not doing so does not seem to cause error.
this.loadStripe = this.loadStripe.bind(this);
}
loadStripe(onload) {
if(! window.StripeCheckout) {
const script = document.createElement('script');
script.onload = function () {
console.info("Stripe script loaded");
onload();
};
script.src = 'https://checkout.stripe.com/checkout.js';
document.head.appendChild(script);
} else {
onload();
}
}
componentDidMount() {
this.loadStripe(() => {
this.stripeHandler = window.StripeCheckout.configure({
key: 'pk_test_xxxxxxxxxxxxxxxxxxxxxxxx',
image: 'https://stripe.com/img/documentation/checkout/marketplace.png',
locale: 'auto',
token: (token) => {
this.setState({ loading: true });
// use fetch or some other AJAX library here if you dont want to use axios
axios.post('/your-server-side-code', {
stripeToken: token.id,
});
}
});
this.setState({
stripeLoading: false,
// loading needs to be explicitly set false so component will render in 'loaded' state.
loading: false,
});
});
}
componentWillUnmount() {
if(this.stripeHandler) {
this.stripeHandler.close();
}
}
onStripeUpdate(e) {
this.stripeHandler.open({
name: 'test',
description: 'widget',
panelLabel: 'Update Credit Card',
allowRememberMe: false,
});
e.preventDefault();
}
render() {
const { stripeLoading, loading } = this.state;
return (
<div>
{(loading || stripeLoading)
? <p>loading..</p>
: <button onClick={this.onStripeUpdate}>Add CC</button>
}
</div>
);
}
}

Categories

Resources