Error handling with Async/Await in Stripe API JS - javascript

I'm playing with the Stripe API. Everything works great, but I want to show possible configurations fails in the frontend. Those fails are handled as an 500.
So this is the condensed code
JS
async function initialize() {
const { clientSecret } = await fetch("index.php?format=json", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: myid, lang: mylang }),
}).then((r) => {
if (r.status >= 400 && r.status < 600) {
// throw new Error(r.status);
}
return r.json()
}
).catch((error) => {
console.log(error)
}
);
console.log(clientSecret);
}
PHP
header('Content-Type: application/json');
try {
// code doing some stuff ...
$output = [
'secret' => 12345
];
echo json_encode($output);
} catch (Error $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
When having a misconfiguration, this is (for example) what the fetch call outputs:
{
"error": true,
"code": 0,
"message": "The payment method type provided: acss_debit is invalid. This payment method is available to Stripe accounts in CA and US and your Stripe account is in XX.",
}
So how can I catch that error message in JS and display it to the user?
I tried all kind of stuff in the initialize function.

I managed to access the catched error message by doing this:
if (r.status >= 400 && r.status < 600) {
return r.text()
.then((text) => {
throw (JSON.parse(text));
});
}

Related

React with Node getting json data from res.status.json()

I am running a react app with nodejs acting as an api to connect to my database.
For my log in I am sending data to the server, and it is returning a pass or fail.
However I am not sure how to extract this json object.
I have looked at the request and response, and as I have manipulated the json object the response content-length has been changing so I believe it must be there somewhere.
SERVER CODE:
app.post('/api/checkLogin', async (req,res) => {
console.log(req.body);
const {username, password} = req.body;
try{
let state = await DB.checkPassword(username, password);
console.log(state);
if(!state){
res.status(401).json({
error: 'Incorrect username or password',
yay: 'idk work?'
});
}
else if(state){
res.status(200).json({
message: 'we in boys'
});
} else {
res.status(6969).json({
err: 'idk mane'
});
}
} catch(e) {
console.log(e);
}
})
CLIENT CODE:
onSubmit = (event) => {
event.preventDefault();
fetch('/api/checkLogin', {
method:'POST',
body: JSON.stringify({username: this.state.username, password: md5(this.state.password)}),
headers: {
'Content-Type':'application/json'
}
}).then(res => {
if(res.status ===200) {
this.props.loggedIn();
} else if(res.status ===401){
console.log(res.status);
alert('wrong username or password');
}else{
const error = new Error(res.error);
throw error;
}
}).catch(err => {
console.log(err);
alert(err);
});
}
What I was sort of expecting as a way to extract the data would be.
On the server:
res.status(200).json({ message : 'mssg'});
On the client:
console.log(res.status.message) // 'mssg'
Thanks Jin and this post I found for the help Fetch API get raw value from Response
I have found that both
res.status(xxx).json({ msg: 'mssg'}) and res.status(xxx).send({msg: 'mssg'}) work.
The json, or sent message can then be interpreted on the client side with a nested promise. This is done with...
fetch('xxx',headers n stuff).then(res => {
res.json().then((data) => {console.log(data.message)});
//'mssg'
res.text().then((data) => { let data1 = JSON.parse(data); console.log(data1.message);});
//'mssg'
});
According to my experience, using res.status(200).send({message: 'mssg'}) is better.
And you can get data after calling api by using res.data.
Then you can get result as below:
{
message: 'mssg'
}
Here is something that may help.
onSubmit = (event) => {
event.preventDefault();
const userData = {
username: this.state.username, // I like to store in object before passing in
password: md5(this.state.password)
}
fetch('/api/checkLogin', {
method:'POST',
body: JSON.stringify(userData), //stringify object
headers: {
'Content-Type':'application/json'
}
}).then(res => res.json()) // convert response
.then(responseData => {
let status = responseData.whatObjectWasPassedFromBackEnd;
status === 200 ? do something on pass: do something on fail
})
.catch(err => {
console.log(err);
alert(err);
});
}

res.send is not returning the expected data: JavaScript, Express, Node?

I have a post request that submits a patient name and the server is supposed to give me back patient_id in response. I get a 200 response back to the client however I don't get the patient_id back which is what I need. When I console log on the server i can see patient.id is generated and there are no errors either. Wonder if there is something I am missing?
Response -
body: (...), bodyUsed: false, headers: Headers {}, ok: true, redirected: false, status: 200, statusText: "OK", type: "basic", url: "http://localhost:4000/patient/add"
//client side post
handleSubmit(e) {
e.preventDefault();
const postUrl = '/patient/add';
fetch(postUrl, {
method: 'POST',
headers: {'Content-Type': 'text/plain'},
body: this.state.patientName
})
.then(response=> {
if (!response.ok) console.log('failed', response);
else console.log(response);
});
}
this.app.post('/patient/add', bodyParser.text(),
this.route_post_patient_add.bind(this));
async route_post_patient_add(req, res) {
/** #type {string} */
const body = req.body;
if (body === undefined) {
logger.warning('Set room patient failed, body missing');
res.sendStatus(400);
return;
}
if (body === "") {
logger.warning(' body is empty');
res.sendStatus(400);
return;
}
try {
const patient_id = await this.save_patient(body);
res.send(patient_id);
console.log(patient_id); //logs the id that is generated
}
catch (err) {
logger.error('Set patient failed, internal error', { err });
res.sendStatus(500);
}
}
The response object in fetch is not the raw body.
You have to call a function and resolve a promise to get the data.
For example:
fetch("foo")
.then(parse_body)
.then(log_data);
function parse_body(response) {
return response.text();
}
function log_data(response_text) {
console.log(response_text);
}
Further reading: MDN: Using Fetch

Stripe - Payment Intents (3d secure issue)

I did implement Payment intents on my website and now works perfectly with this testing card 4242 4242 4242 4242, but for other cards that need 3d secure methods, I take this error "Invalid PaymentIntent status".
the Code that I have used is the same standard code that exists on the Stripe documentation-flow enriched with some code to manage mysql, emails, metadata etc.
Where do I go wrong? Thanks in Advance.
simplified js code connected to index.php
var stripe = Stripe('pk_test_xxx');
var elements = stripe.elements();
var cardElement = elements.create('card', {style: style});
cardElement.mount('#card-element');
var cardholderName = document.getElementById('cardholder-name');
var cardButton = document.getElementById('card-button');
var amount = $('#amount').val();
cardButton.addEventListener('click', function(ev) {
ev.preventDefault();
stripe.createPaymentMethod('card', cardElement, {
billing_details: {name: cardholderName.value}
}).then(function(result) {
if (result.error) {
} else {
$body.addClass("loading");
fetch('https://test.com/server.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
payment_method_id: result.paymentMethod.id,
amount: amount
})
}).then(function(result) {
// Handle server response (see Step 3)
result.json().then(function(json) {
handleServerResponse(json);
})
});
}
});
});
function handleServerResponse(response) {
if (response.error) {
} else if (response.requires_action) {
stripe.handleCardAction(
response.payment_intent_client_secret
).then(function(result) {
if (result.error) {
} else {
// The card action has been handled
// The PaymentIntent can be confirmed again on the server
fetch('https://test.com/server.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
payment_method_id: result.paymentMethod.id,
amount: amount
})
}).then(function(confirmResult) {
console.log(confirmResult);
return confirmResult.json();
}).then(handleServerResponse);
}
});
} else {
}
}
simplified code on server.php
<?php
# vendor using composer
require_once('stripe6400/init.php');
\Stripe\Stripe::setApiKey('sk_test_xxx');
header('Content-Type: application/json');
# retrieve json from POST body
$json_str = file_get_contents('php://input');
$json_obj = json_decode($json_str);
$paymentid = $json_obj->payment_method_id;
$amount = $json_obj->amount;
$intent = null;
try {
if (isset($json_obj->payment_method_id)) {
# Create the PaymentIntent
$intent = \Stripe\PaymentIntent::create([
'payment_method' => $json_obj->payment_method_id,
'amount' => $json_obj->amount,
'payment_method_types' => ["card"],
'currency' => 'gbp',
'confirmation_method' => 'manual',
'confirm' => true,
]);
}
if (isset($json_obj->payment_intent_id)) {
$intent = \Stripe\PaymentIntent::retrieve(
$json_obj->payment_intent_id
);
$intent->confirm();
}
generatePaymentResponse($intent);
} catch (\Stripe\Error\Base $e) {
# Display error on client
echo json_encode([
'error' => $e->getMessage()
]);
}
function generatePaymentResponse($intent) {
if ($intent->status == 'requires_action' &&
$intent->next_action->type == 'use_stripe_sdk') {
echo json_encode([
'requires_action' => true,
'payment_intent_client_secret' => $intent->client_secret
]);
} else if ($intent->status == 'succeeded') {
Stripe\Customer::create([
"email" => $email,
"name" => $customer_name,
"source" => "tok_visa" // obtained with Stripe.js
]);
echo json_encode([
"success" => true
]);
} else {
# Invalid status
http_response_code(500);
echo json_encode(['error' => 'Invalid PaymentIntent status']);
}
}
?>
It looks like you might have the same error I just had. The status of the response from stripe is requires_source_action not requires_action so your if statement falls through to Invalid PaymentIntent status.
// change this
// $intent->status == 'requires_action'
// to this
$intent->status == 'requires_source_action'
In my case I'm checking for both so my code is ready for when I do update the stripe SDK.
https://stripe.com/docs/payments/payment-intents/quickstart#confirm-again
(line 33 in the code)
Also on Customer::create your source attribute "tok_visa" must be a real token.id from create token in javascript https://stripe.com/docs/stripe-js/reference#stripe-create-token

HTTP request not working when done in code

I am making a request and everything is correct but the issue I have is I keep getting a 404 error. but if I copy the parameters and body with that same url to postman it returns a success. I do not know what I am doing wrong.
const promisify = require('util').promisify;
const { post, get, del } = require('request');
const postAsync = promisify(post);
post: async (url, payload) => {
console.log('USER ID PAYLOAD',payload)
return postAsync(url, {
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
json: true,
body: payload
})
.then(response => {
console.log('USER ID StAtUS CODE', response.url, response.body)
if (response.statusCode >= 400) throw new Error(response.body.message || "An error occurred");
if (response.body && response.body.status === 'error' || response.body.status === 'fail') throw new Error(response.body.message || "An error occurred");
return response.body;
}, error => {
throw error;
})
},

Fetch request doesn't return JSON from an endpoint

I'm trying to get a JSON data from an endpoint located in route /users at the same domain but somehow I always get error:
Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
When I take a look at location of the error I'm getting the source of the index page (same page where script is ran).
This is the route (Hapi.js):
allUsers: {
method: 'GET',
path: '/users',
options: AuthHelper.required,
handler: async (request, h) => {
let users = [];
let error = false;
await User.find({})
.lean().then(result => {
if (result && result.length) users = db.filterResult(result);
})
.catch(err => {
error = true;
console.log(err);
});
if (error) {
return { message: 'There is an error.' };
} else {
return { users: users };
}
}
}
This is the client side code:
fetch('http://localhost:3000/users').then((response) => response.json()).then((json) => console.log(json));
When I visit the URL in a browser directly, I get the results.
By the way, you should use the reply interface method, so that hapi will automatically convert it into valid JSON:
allUsers: {
method: 'GET',
path: '/users',
options: AuthHelper.required,
handler: async (request, h) => {
let users = [];
let error = false;
await User.find({})
.lean().then(result => {
if (result && result.length) users = db.filterResult(result);
})
.catch(err => {
error = true;
console.log(err);
});
if (error) {
return { message: 'There is an error.' };
} else {
reply( { users: users } );
}
}
}
I realized where the problem is thanks to charlieftl. I opened up Network tab and followed where request was going - it was a login page. There were no credentials sent with request (and as it can be seen from the code, route is protected) so response always returned source of index page because I was redirected back.
I just had to add same-origin property to a credentials option.
fetch('http://localhost:3000/users', { credentials: 'same-origin' }).then((response) => response.json()).then((json) => console.log(json));

Categories