React js Stripe checkout is not working - javascript

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>
);
}
}

Related

Stimulus Script tag not executed for content appended to the page after the page is fully loadedd

I have a stimulus controller responsible for fetching and appending html to the page.
remote_ui_controller.js
import { Controller } from '#hotwired/stimulus';
import axios from 'axios';
export default class extends Controller {
static values = {
containerSelector: String,
url: String,
params: Object,
uiLoaded: { type: Boolean, default: false }
}
fetchUI(){
if (this.uiLoadedValue) { return; }
axios.post(this.urlValue, this.paramsValue)
.then((response) => {
this.uiLoadedValue = true
this.container().innerHTML = response.data
}).catch((err) => {
this.uiLoadedValue = false
this.displayError()
})
}
container() {
return document.querySelector(this.containerSelectorValue);
}
displayError(){
this.container().innerHTML = this.errorTemplate()
}
errorTemplate() {
return `<p class='text-red-500'>An error happened, please try again later</p>`
}
}.
Now in my specific case it renders this rails partial :
<%= render(
'organizations/rows/program/available_sessions',
product: #product,
learning_item: #program,
show_enroll_button: #show_enroll_button,
button_type: #button_type,
form: #form
)
%>
<script type="text/javascript">
alert('helllo')
console.log("heyyyyyy")
tippy('.collaborators-enrolled', {
// default
placement: 'right-start',
allowHTML: true,
arrow: true,
theme: 'light',
});
</script>
it works well but the code in the script tag is never executed. Why is this script tag not executed ?

Mitrhil.js conditional routing and authentication

I'm studying javascript and mithril.js 1.1.6. I'm writing down a simple web app in which users land on a page where he can login. Users who already did login land on a different page. I'm trying this using conditional routing, here is the main component:
const m = require("mithril");
...
import Eventbus from './whafodi/eventbus.js';
import WelcomePage from './ui/welcome.js';
import User from './model/user.js';
var eventbus = new Eventbus();
function MyApp() {
return {
usrAuth: function() {
m.route(document.body, "/", {
"/": { view: () => m("p", "hello")}
})
},
usrNotAuth: function() {
m.route(document.body, "/", {
"/": { render: v => m(WelcomePage, eventbus) }
})
},
oninit: function(vnode) {
vnode.state.user = new User();
eventbus.subscribe({
type: "login",
handle: function(action) {
vnode.state.user.token = action.token;
console.log(JSON.stringify(vnode.state.user));
}
});
},
view: function(vnode) {
if(vnode.state.user.token) {
this.usrAuth();
} else {
this.usrNotAuth();
}
}
}
};
m.mount(document.body, MyApp);
MyApp is the main component. It check if user has a token, then return the proper route. This is the component that is in charge to let users login:
const m = require("mithril");
const hellojs = require("hellojs");
function TopBar(node) {
var bus = node.attrs.eventbus;
function _login() {
hellojs('facebook').login({scope:'email'});
}
return {
oninit: function(vnode) {
hellojs.init({
facebook: XXXXXXX,
}, {
redirect_uri: 'http://localhost'
});
hellojs.on('auth.login', auth => {
var fbtoken = auth.authResponse.access_token;
m.request({
method:"POST",
url:"./myapp/login/fb/token",
data:auth.authResponse,
background: true
}).then(function(result){
console.log(result);
bus.publish({ type: "login", token: result.jwttoken });
m.route.set("/");
}, function(error){
console.log(error);
bus.publish({ type: "login", token: "" });
});
});
},
view: function(vnode) {
return m("div", [
m("button", { onclick: _login }, "Login")
]);
}
}
}
export default TopBar;
TopBar component occurs in the WelcomePage component mentioned in the main one. TopBar renders a button and use hello.js to login. It uses the EventBus bus parameter to tell main component user logged in (there is an handler in main component to update the user model). Once user logins, event is fired and main component updates the user model. Good. Now, how can trigger the main component to load the right route?
I read mithril'docs again and I found that RouteResolvers perfectly suit my needs. Here is an example:
var App = (function() {
var login;
function isLoggedIn(component) {
if(login) {
return component;
} else {
m.route.set("/hey");
}
}
return {
oninit: function(vnode) {
EventBus.subscribe({
type: "login",
handle: function(action) {
console.log("incoming action: " + JSON.stringify(action));
login = action.value;
}
});
},
oncreate: function(vnode) {
Foo.eventbus = EventBus;
Bar.eventbus = EventBus;
Hey.eventbus = EventBus;
m.route(document.body, "/hey", {
"/foo": {
onmatch: function(args, requestedPath, route) { return isLoggedIn(Foo); }
},
"/bar": {
onmatch: function(args, requestedPath, route) { return isLoggedIn(Bar); }
},
"/hey": Hey
});
},
view: function(vnode) {
return m("div", "home..");
}
};
})();
Eventbus is used to let components communicate with App. They fire events (login type events) that App can handle. I found convenient to pass Eventbus the way oncreate method shows, I can use Eventbus in each component's oncreate to let components fire events.

Property not defined with VueJS mixins

I try to use Mixins with Vue.js. But I encounter several issues with them :/
This is my current code for my two test modules :
ErrorBaseMixin.vue
<script>
import ErrorAlert from './ErrorAlert';
export const ErrorBaseMixin = {
data() {
return {
// Errors management
error_display: true,
error_data: {
level: "warning",
time: 0,
status: 200,
message: ""
}
}
},
methods: {
// ------------------------------------------------------------------------
// Errors management functions
// ------------------------------------------------------------------------
error_function_show_error: function() {
try {
this.$refs.error_component.launch();
}
catch {}
},
callback_error_catched: function(e) {
if(e.message === 'Network Error'){
this.error_data.message = "<strong>There was a network error :</strong> The connection is broken or the server is not started.";
this.error_data.level = "danger";
}
else {
this.error_data.message = "An error occured : " + e.message;
this.error_data.level = "warning";
}
this.error_function_show_error();
},
},
components: {
ErrorAlert
}
}
export default ErrorBaseMixin;
</script>
Test.vue
<template>
<ErrorAlert
:error_display="error_display"
:error="error_data"
ref="error_component"
/>
</div>
</template>
<script lang="js">
import {ErrorBaseMixin} from '../../../parts/ErrorBaseMixin.vue';
export default {
mixins: [ErrorBaseMixin],
name: 'Test_elt',
created() {
this.REST_ADDR = "test/test";
},
data() {
return {
field: {
id: '55',
name: 'test'
}
}
},
methods: {
}
}
</script>
But when I compile the last module, I have the following errors in my browser console :
[Vue warn]: Property or method "error_data" is not defined on the
instance but referenced during render. Make sure that this property is
reactive, either in the data option or for class-based components, by
initializing the property.
[Vue warn]: Unknown custom element: - did you register
the component correctly? For recursive components, make sure to
provide the "name" option.
But... Everything is working fine. So I don't understand why I have these errors
You must change ErrorBaseMixin.vue to ErrorBaseMixin.js:
import ErrorAlert from './ErrorAlert';
const ErrorBaseMixin = {
data() {
return {
// Errors management
error_display: true,
error_data: {
level: "warning",
time: 0,
status: 200,
message: ""
}
}
},
methods: {
// ------------------------------------------------------------------------
// Errors management functions
// ------------------------------------------------------------------------
error_function_show_error: function() {
try {
this.$refs.error_component.launch();
}
catch {}
},
callback_error_catched: function(e) {
if(e.message === 'Network Error'){
this.error_data.message = "<strong>There was a network error :</strong> The connection is broken or the server is not started.";
this.error_data.level = "danger";
}
else {
this.error_data.message = "An error occured : " + e.message;
this.error_data.level = "warning";
}
this.error_function_show_error();
},
},
components: {
ErrorAlert
}
}
export default ErrorBaseMixin;
And then import in your component:
import {ErrorBaseMixin} from '../../../parts/ErrorBaseMixin.js';
export default {
mixins: [ErrorBaseMixin],
...
Note: Take care how import and export, I have changed the way.

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

Aurelia Google SignIn Button

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>

Categories

Resources