I use the samples (https://github.com/stripe-samples/checkout-single-subscription/tree/master/server/php) from Stripe to create a subscription. What I don't really understand, how can I pass metadata from my index.html over script.js to the create-checkout-session.php.
I thought I just add data attributes to the index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Stripe</title>
<meta name="description" content="A demo of Stripe Payment Intents" />
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<script src="https://js.stripe.com/v3/"></script>
<script src="./script.js" defer></script>
</head>
<body>
<div class="sr-root">
<div class="sr-main" style="display: flex;">
<div class="sr-container">
<section class="container">
<button id="basic-plan-btn" data-partner="name" data-package="basic">USD 6.90</button>
</section>
<section class="container">
<button id="pro-plan-btn" data-partner="name" data-package="premium">USD 11.90</button>
</section>
</div>
</div>
</div>
</body>
</html>
then I have to read them somehow out in the script.js. But that I don't really figure out how.
// Create a Checkout Session with the selected plan ID
var createCheckoutSession = function(priceId) {
return fetch("/fileadmin/restaurant/stripe/create-checkout-session.php", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
priceId: priceId,
partner: 'name',
package: 'premium'
})
}).then(function(result) {
return result.json();
});
};
// Handle any errors returned from Checkout
var handleResult = function(result) {
if (result.error) {
var displayError = document.getElementById("error-message");
displayError.textContent = result.error.message;
}
};
/* Get your Stripe publishable key to initialize Stripe.js */
fetch("/fileadmin/restaurant/stripe/config.php")
.then(function(result) {
return result.json();
})
.then(function(json) {
var publishableKey = json.publishableKey;
var basicPriceId = json.basicPrice;
var proPriceId = json.proPrice;
var stripe = Stripe(publishableKey);
// Setup event handler to create a Checkout Session when button is clicked
document
.getElementById("basic-plan-btn")
.addEventListener("click", function(evt) {
createCheckoutSession(basicPriceId).then(function(data) {
// Call Stripe.js method to redirect to the new Checkout page
stripe
.redirectToCheckout({
sessionId: data.sessionId
})
.then(handleResult);
});
});
// Setup event handler to create a Checkout Session when button is clicked
document
.getElementById("pro-plan-btn")
.addEventListener("click", function(evt) {
createCheckoutSession(proPriceId).then(function(data) {
// Call Stripe.js method to redirect to the new Checkout page
stripe
.redirectToCheckout({
sessionId: data.sessionId
})
.then(handleResult);
});
});
});
by that I receive them in the create-checkout-session.php
<?php
require_once 'shared.php';
$domain_url = $config['domain'];
$checkout_session = \Stripe\Checkout\Session::create([
'success_url' => $domain_url . 'success.php?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => $domain_url . 'canceled.php',
'payment_method_types' => ['card'],
'mode' => 'subscription',
'allow_promotion_codes' => true,
'line_items' => [[
'price' => $body->priceId,
'quantity' => 1,
]],
'subscription_data' => ['trial_period_days' => 60],
'metadata' => [
'partner' => $body->partner,
'package' => $body->package
],
]);
echo json_encode(['sessionId' => $checkout_session['id']]);
Thank You.
What you've done adding to the JSON body of the fetch call looks right to me. If you're trying to set the 'name' and 'premium' values dynamically from some input, then take a look at this previous answer for some approaches for getting input values.
Related
I have to add a list of data structures to a js object on an HTML page.
I have a list of data structures on the server side each identified by a data-key.
A js function loops the list of data-keys
the function gets each structure one at a time from a async_cache using the corresponding data-key.
There may be multiples of same data-keys in the list
if the async_cache doesn't have the data-structure by that data-key it async-fetches and adds it to the cache.
if the async_cache has the data-structure by that data-key it returns it right away.
if the async_cache has already requested a fetch for a data-key and before the data arrives has another request for the same data-key it must not duplicate the request but wait until the data arrives.
I have constructed the following code so far (as an example for discussion). The actual environment can't be shared.
<?php
switch($_GET['--action'] ?? false){
case 'data-structure':{
$data_group = [
'data-01' => ['a' => 'info-01', 'b' => 'extra-01'],
'data-02' => ['a' => 'info-02', 'b' => 'extra-02'],
'data-03' => ['a' => 'info-03', 'b' => 'extra-03'],
'data-04' => ['a' => 'info-04', 'b' => 'extra-04'],
'data-05' => ['a' => 'info-05', 'b' => 'extra-05'],
];
if($data_struct = ($data_group[$_GET['key'] ?? false] ?? false)){
\header('Content-Type: application/json');
echo json_encode(['status'=> 'ok', 'data' => $data_struct], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
exit();
} else {
http_response_code(404);
echo json_encode('Not Found', JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
exit();
}
} break;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta key="viewport" content="width=device-width, initial-scale=1.0">
<title>JS Async Data Buffer Experiment</title>
</head>
<body>
<div class="container">
</div>
<script>
xui = {};
xui.async_cache = {
async async_get_await(url = '', options = {}){
// Default options are marked with *
const response = await fetch(
url,
{
method: 'GET', // *GET, POST, PUT, DELETE, etc.
mode: options.mode || 'same-origin', // no-cors, *cors, same-origin
cache: options.cache || 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: options.credintials || 'same-origin', // include, *same-origin, omit
redirect: options.redirect || 'follow', // manual, *follow, error
referrerPolicy: options.referrerPolicy || 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
}
);
return response.json(); // parses JSON response into native JavaScript objects;
},
get(action, key){
if(key in this){
return new Promise((resolve, reject) => {
resolve(this[key]);
});
} else {
return this.async_get_await(
`?--action=${action}&key=${key}`,
).then((r) => {
this[key] = r.data;
return this[key];
});
}
},
};
window.addEventListener('DOMContentLoaded', function(){
var list = [
'data-01',
'data-01',
'data-02',
'data-01',
'data-03',
'data-02',
'data-04',
'data-02',
'data-01',
];
list.forEach((key) => {
console.log({key});
xui.async_cache.get('data-structure', key).then((data) => {
console.log(data);
});
});
});
</script>
</body>
</html>
Case 1 and 2 are taken care of but case 3 is where I am stuck. As you can see the code in the addEventListner executes in one shot before the data is received or event-loop kicks-in. So multiple requests go out for the same data-key.
I am looking for a minimalist approach. If there is a coding feature in JS library without using third party library - that will do as well.
I've also looked into the cache option on the Fetch-API but that is not a solution.
Thanks!
Thanks to #Bergi I finally figured it out!
I rewrote the async_cache and went on simplifying ...
Here is what I got (I've added a few test buttons as well):
<?php
switch($_GET['--action'] ?? false){
case 'data-structure':{
$data_group = [
'data-01' => ['a' => 'info-01', 'b' => 'extra-01'],
'data-02' => ['a' => 'info-02', 'b' => 'extra-02'],
'data-03' => ['a' => 'info-03', 'b' => 'extra-03'],
'data-04' => ['a' => 'info-04', 'b' => 'extra-04'],
'data-05' => ['a' => 'info-05', 'b' => 'extra-05'],
];
if($data_struct = ($data_group[$_GET['key'] ?? false] ?? false)){
\header('Content-Type: application/json');
echo json_encode(['status'=> 'ok', 'data' => $data_struct], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
exit();
} else {
http_response_code(404);
echo json_encode('Not Found', JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
exit();
}
} break;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta key="viewport" content="width=device-width, initial-scale=1.0">
<title>JS Async Data Cache Example</title>
</head>
<body>
<div class="container">
<button type="button" onclick="run()">Run</button> <br/>
<button type="button" onclick="delete xui.async_cache['data-01']">Delete 01</button> <br/>
<button type="button" onclick="delete xui.async_cache['data-02']">Delete 02</button> <br/>
<button type="button" onclick="delete xui.async_cache['data-03']">Delete 03</button> <br/>
<button type="button" onclick="delete xui.async_cache['data-04']">Delete 04</button> <br/>
Look at the console for responses and check the network tab as well!
</div>
<script>
xui = {};
xui.async_cache = {
get(key){
return this[key] ?? (this[key] = (fetch(`?--action=data-structure&key=${key}`).then((response) => {
return response.json();
})));
}
};
function run(){
var list = [
'data-01',
'data-01',
'data-02',
'data-01',
'data-03',
'data-02',
'data-04',
'data-02',
'data-01',
];
list.forEach((key) => {
console.log({key});
xui.async_cache.get(key).then((data) => {
console.log(data);
});
});
}
window.addEventListener('DOMContentLoaded', function(){
run();
});
</script>
</body>
</html>
Looks like we can take advantage of the closure's data storage (or caching) inside the promise. And we don't need await or async, atleast for this rudimentary stage.
This is my server side code
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
use Slim\Http\Request;
use Slim\Http\Response;
use Stripe\Stripe;
require 'vendor/autoload.php';
$dotenv = Dotenv\Dotenv::create(__DIR__);
$dotenv->load();
require './config.php';
$app = new \Slim\App;
$app->add(function ($request, $response, $next) {
Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
return $next($request, $response);
});
$app->get('/', function (Request $request, Response $response, array $args) {
return $response->write(file_get_contents(getenv('STATIC_DIR') . '/index.html'));
});
$app->post('/checkout_sessions', function(Request $request, Response $response) use ($app) {
$params = json_decode($request->getBody());
$payment_method_types = [
'usd' => ['card'],
'eur' => ['card'],
'cad' => ['card']
];
$products = [
'cause-a' => 'prod_KP3YP2a3IGYqsb',
'cause-b' => 'prod_KP3iZRGcEjn5W8',
];
$session = \Stripe\Checkout\Session::create([
'success_url' => 'http://localhost:4242/success.html?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => 'http://localhost:4242/?cancel=true',
'mode' => 'payment',
'payment_method_types' => $payment_method_types[$params->currency],
'metadata' => [
'cause' => $params->cause,
'currency' => $params->currency,
],
'submit_type' => 'donate',
'line_items' => [[
'price_data' => [
'currency' => $params->currency,
'product' => $products[$params->cause],
'unit_amount' => $params->amount,
],
'quantity' => 1,
]]
]);
return $response->withJson([
'id' => $session->id
]);
});
$app->get('/order', function (Request $request, Response $response) {
$id = $_GET['sessionId'];
$checkout_session = \Stripe\Checkout\Session::retrieve($id);
echo json_encode($checkout_session);
});
$app->run();
this is the success page with javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Order Confirm</title>
</head>
<body>
<div id="main">
<div id="checkout">
<div id="payment-forum">
<h1>Success</h1>
payment status: <span id="payment-status"></span>
<pre>
</pre>
</div>
</div>
</div>
</body>
<script>
var paymentStatus = document.getElementById('payment-status');
var urlParams = new URLSearchParams(window.location.search);
var sessionId = urlParams.get("session_id")
if (sessionId) {
fetch("/order?sessionId=" + sessionId).then(function(result){
return result.json()
}).then(function(session){
var sessionJSON = JSON.stringify(session, null, 2);
document.querySelector("pre").textContent = sessionJSON;
}).catch(function(err){
console.log('Error when fetching Checkout session', err);
});
}
</script>
</html>
i need help with product detail , customer name , amount on success page and if possible payment paid or not paid status on it.... cant find any tutorial on it or any detail step by step guide on it
i am close but cant get it to bullseyes please help me with it
yeah on success page all i get is payment status and blank
Looks like here is what happened:
Your Checkout Session defined success_url to success.html
Your success.html fired another request to "/success?session_id=xxx"
Your backend handled this with $app->get('/success', function...
It could be confusing to name both HTML in step 2 and the handling URL in step 3 as "success". You may want to use a different name such as "success" and "get-checkout-session" like Stripe example on Github. After that, debug your server log on step 3 to see whether you got the session id correctly and have retrieved the Customer Id.
There is more information to extract from a CheckoutSession. See Stripe's API Reference on available properties. You probably want to expand its payment_intent to see the amount and payment status.
Context
I've created a two column page, one column with a button on the left and editorJS on the right column.
What I am trying to do is whenever I click on the button to the left, I want it to be copied into a new block of EditorJS.
Replication
The error triggers whenever trying to add a new block manually (in this case, an extertal button to add more content to EditorJS) using editor.blocks.insert(blockToAdd). Independently of the configuration of the EditorJS, even the most basic one will trigger this error.
Code
index.js (I've modified this file to make it shorter, the error always trigger in the same place which is
const blockToAdd = {
type: 'paragraph',
data: {
text: 'My header'
}
};
editor.blocks.insert(blockToAdd); // Here
Actual file
const btn = document.getElementById('export-btn')
const editor = new EditorJS({
/**
* Id of Element that should contain Editor instance
*/
onReady: () => {
new DragDrop(editor);
},
holder: 'editorjs',
tools: {
header: {
class: Header,
config: {
placeholder: 'Enter a header',
levels: [1,2,3,4],
defaultLevel: 3
}
},
paragraph: {
class: Paragraph,
inlineToolbar: true,
}
},
});
function saveEditor() {
editor.save().then( savedData => {
fetch('http://localhost:3000', {
method: 'POST',
body: JSON.stringify(savedData),
headers: {
'Content-Type': 'application/json'
}
}).then( msg => {
console.log(msg)
}).catch( err => {
console.error(err)
})
console.log()
})
.catch( err => {
console.error(err)
})
}
btn.addEventListener('click', function() {
const blockToAdd = {
type: 'paragraph',
data: {
text: 'My header'
}
};
editor.blocks.insert(blockToAdd);
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>Testing EditorJS and Docx</title>
</head>
<body>
<div class="col-left">
<div id="export-btn" class="export-btn">Export to...</div>
</div>
<div class="col-right">
<div id="editorjs"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/#editorjs/editorjs#latest"></script>
<script src="https://cdn.jsdelivr.net/npm/#editorjs/header#latest"></script>
<script src="https://cdn.jsdelivr.net/npm/#editorjs/list#latest"></script>
<script src="index.js"></script>
</body>
</html>
Error
Uncaught TypeError: can't access property "name", s is undefined
S https://cdn.jsdelivr.net/npm/#editorjs/editorjs#latest:6
value https://cdn.jsdelivr.net/npm/#editorjs/editorjs#latest:6
value https://cdn.jsdelivr.net/npm/#editorjs/editorjs#latest:6
<anonymous> https://cdn.jsdelivr.net/npm/#editorjs/editorjs#latest:6
<anonymous> file:///E:/wdev/tasktag-dashboard-ui/index.js:105
EventListener.handleEvent* file:///E:/wdev/tasktag-dashboard-ui/index.js:97
editorjs#latest:6:44399
Error screenshot
Where could the problem be at? Because I am using <script src="https://cdn.jsdelivr.net/npm/editorjs-drag-drop#latest"></script> sort of imports?
Otherwise, I don't know what the problem is. Thank you in advance.
So the problem was in the object I pass to the editor.blocks.insert(blockToInsert).
It requires to pass option by option instead a single object. So the correct solution would be:
const blockToAdd = {
type: 'paragraph',
data: {
text: 'My header'
}
};
editor.blocks.insert(blockToAdd.type, blockToAdd.data);
Thanks Thomas from a JavaScript Telegram community for helping me out to resolve this problem.
I have a custom form that makes a few requests to a database to verify the user. I noticed that if I have a single google account it works fine but it doesn't with multiple. The other thing I noticed is that the script doesn't throw any error it just doesn't communicate back the result from the custom form.
This is how my custom forms look like:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<div class="container select-client">
<div class="client">Client</div>
<select class="client-select">
<option>Select Client</option>
<!-- ...options -->
</select>
<div class="market">Market</div>
<select class="market-select">
<option>Select Market</option>
<!-- ...options -->
</select>
<div class="error-message"></div>
<button class="button" id="select-button" onclick="handleSelect()">Select</button>
</div>
<script>
// ...code to validate the user
function handleSelect() {
var _client = clients.find(
(client) => client.id === parseInt(selectedClient)
);
var _market = markets.find(
(market) => market.id === parseInt(selectedMarkets)
);
if (!_client && !_market) {
return;
}
if (!_client) {
errorMessageClientMarket.innerHTML = 'Please select client';
return;
}
if (!_market) {
errorMessageClientMarket.innerHTML = 'Please select market';
return;
}
google.script.run
.withSuccessHandler()
.loginData({ token, market: _market, client: _client, user: userInfo, platform });
google.script.host.close();
}
</script>
</body>
</html>
This is how I create the custom form using app script
const loginForm = () => {
const html = HtmlService.createHtmlOutputFromFile('loginFormHtml')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
.setWidth(600)
.setHeight(600);
const ui = SpreadsheetApp.getUi();
ui.showModalDialog(html, `Login`);
};
This is the callback function:
const loginData = (data) => { // <--- this function is ignored when a the users has multiple google accounts
console.log('LOGIN FORM');
const { token, market, user, client, platform } = data;
UserProperties.setProperty('token', token);
UserProperties.setProperty('userId', user.id);
UserProperties.setProperty('clientId', client.id);
UserProperties.setProperty('clientName', client.name);
UserProperties.setProperty('marketId', market.id);
UserProperties.setProperty('marketName', market.code_name);
UserProperties.setProperty('username', `${user.first_name} ${user.last_name}`);
UserProperties.setProperty('userEmailAddress', user.email);
UserProperties.setProperty('platform', platform);
const info = UserProperties.getProperties();
console.log('info ---> ', info)
const ui = SpreadsheetApp.getUi();
getMenu(true);
ui.alert('Logged in Successfully');
};
Does anyone know if there's a away to fix this?
Just getting the hang of API calls and fetch and have put together the below code to fetch some info from the Trip Advisor API and log a message to the console using this info.
When I call the fetch request function it logs to the console just fine, but as soon as I wrap it in an event listener callback it no longer executes, why is this?
Appreciate any help!
//This is the fetch function kept in a file names request.js
const findRest = async (reviews, closed) => {
const respond = await fetch(
"https://tripadvisor1.p.rapidapi.com/restaurants/list-by-latlng?limit=30¤cy=EUR&distance=2&lunit=km&lang=en_US&latitude=53.3498&longitude=-6.2603",
{
method: "GET",
headers: {
"x-rapidapi-host": "tripadvisor1.p.rapidapi.com",
"x-rapidapi-key": /* my rapidapi key */
}
}
);
if (respond.status === 200) {
let data = await respond.json();
let newData = await data.data;
let data1 = await newData.filter(
review => parseInt(review.num_reviews) >= reviews
);
let data2 = await data1.filter(close => close.is_closed == closed);
return data2;
} else {
throw new Error("Could not provide results within specified parameters");
}
};
//This is the event listener kept in a file names app.js - both files are active and no problems communicating with each other
document.querySelector(".subButton").addEventListener("click", e => {
e.preventDefault();
console.log("click");
const userReviews = parseInt(document.querySelector(".userRev").value);
const userClose = document.querySelector(".userClose").value;
findRest(userReviews, userClose)
.then(data => {
data.forEach(element => {
console.log(
`${element.name} matches your search criterea and is located at ${element.address}
To make a booking, please call ${element.phone}`
);
});
})
.catch(err => {
console.log(err);
});
});
//HTML below
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>What Wine?</title>
<meta name="author" content="Phil My Glass" />
<meta
name="description"
content="An app to help you find the wine you like or something new based on your preferences"
/>
<meta name="keywords" content="wine" />
<link rel="stylesheet" href="style.css" type="text/css" />
</head>
<body>
<header>
<h1>What Restaurant?</h1>
</header>
<main>
<form>
<input class="userRev" /><br />
<input class="userClose" />
<button class="subButton" type="submit">Find!</button>
</form>
</main>
</body>
<script src="req.js" type="text/Javascript"></script>
<script src="app.js" type="text/Javascript"></script>
</html>
Those two lines look like they could break the thread:
const userReviews = parseInt(document.querySelector(".userRev").value);
const userClose = document.querySelector(".userClose").value;
If either one of document.querySelector(".userRev"), document.querySelector(".userClose") is null, that will be uncaught TypeError.
Will know for sure with the HTML.