I am having difficulties when I try to apply the transfer method so I need your help.
What I want to do is transfer money [IN TEST MODE] from one stripe account to another, I did the exact thing that was written in the documentation looks like this:
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2020-03-02',
maxNetworkRetries: 2,
});
exports.handler = async (event) => {
try {
const paymentIntent = await stripe.paymentIntents.create({
amount: 100,
currency: 'eur',
payment_method_types: ['card'],
transfer_group: '{ORDER10}',
});
// Create a Transfer to the connected account (later):
await stripe.transfers.create({
amount: 70,
currency: 'eur',
destination: '{{ACCOUNT_ID}}',
transfer_group: '{ORDER10}',
});
return {
statusCode: 200,
body: JSON.stringify(paymentIntent),
};
} catch (error) {
console.log({ error });
return {
statusCode: 400,
body: JSON.stringify(error),
};
}
};
What I am getting is an error with saying there is no such destination, which means that the account ID is not available.
I got my other stripe account's ID with the curl command:
curl https://api.stripe.com/v1/account -u {{SK_TEST_KEY}}
this gave me the ID and that's what I pasted in the {{ACCOUNT_ID}} field.
What am I doing wrong? Is it something that this thing can not be done in test mode?
Or somehow I need to connect both of my stripe accounts??
Thanks in advance
I created an account on my dashboard stripe page, the main thing was that it has to be a standard account, filled in with some dummy data which I found thanks to Jonathan Steele, after that the transfer worked.
Related
As the title suggests, I am trying to implement Stripe into my flutter app using the stripe extension for Firebase and using Javascript Firebase Cloud Functions for the server side. I believe the issue is on the server side when I try to create a customer and create a payment intent.
The server side code is here:
const functions = require("firebase-functions");
const stripe = require("stripe")("my test secret key"); // this works fine for the other stripe functions I am calling
exports.stripePaymentIntentRequest = functions.https.onRequest(
async (req, res) => {
const {email, amount} = req.body;
try {
let customerId;
// Gets the customer who's email id matches the one sent by the client
const customerList = await stripe.customers.list({
email: email,
limit: 1,
});
// Checks the if the customer exists, if not creates a new customer
if (customerList.data.length !== 0) {
customerId = customerList.data[0].id;
} else {
const customer = await stripe.customers.create({
email: email,
});
customerId = customer.data.id;
}
// Creates a temporary secret key linked with the customer
const ephemeralKey = await stripe.ephemeralKeys.create(
{customer: customerId},
{apiVersion: "2022-11-15"},
);
// Creates a new payment intent with amount passed in from the client
const paymentIntent = await stripe.paymentIntents.create({
amount: parseInt(amount),
currency: "gbp",
customer: customerId,
});
res.status(200).send({
paymentIntent: paymentIntent.client_secret,
ephemeralKey: ephemeralKey.secret,
customer: customerId,
success: true,
});
} catch (error) {
res.status(404).send({success: false, error: error.message});
}
},
);
Then my client-side code is:
try {
// 1. create payment intent on the server
final response = await http.post(
Uri.parse(
'https://us-central1-clublink-1.cloudfunctions.net/stripePaymentIntentRequest'),
headers: {"Content-Type": "application/json"},
body: json.encode({
'email': email,
'amount': amount.toString(),
}),
);
final jsonResponse = json.decode(response.body);
if (jsonResponse['error'] != null) {
throw Exception(jsonResponse['error']);
}
log(jsonResponse.toString());
//2. initialize the payment sheet
await Stripe.instance.initPaymentSheet(
paymentSheetParameters: SetupPaymentSheetParameters(
paymentIntentClientSecret: jsonResponse['paymentIntent'],
merchantDisplayName: 'Clublink UK',
customerId: jsonResponse['customer'],
customerEphemeralKeySecret: jsonResponse['ephemeralKey'],
style: ThemeMode.dark,
),
);
await Stripe.instance.presentPaymentSheet();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Payment completed!')),
);
} catch (e) {
if (e is StripeException) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error from Stripe: ${e.error.localizedMessage}'),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
}
}
I basically copied the flutter_stripe documentation to create the payment sheet with the necessary changes. Any help would be greatly appreciated!
Ok so I found what worked! I was being given a 403 status error with reason "forbidden". This meant I had to go to the google cloud console and update the permissions in the cloud functions tab.
I have integrated stripe api into my ecommerce website. When you checkout you are sent to the stripe api payment link where you type in your information. Of course two things could happen here, either the payment goes through and succeed or the order gets canceled. Everything works except I am trying to remove all cart items only if the payment succeeds and goes through.
Here is my code:
app.post('/api/createCheckoutSession', async (req, res) => {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
mode: 'payment',
line_items: req.body.items.map(item => {
const storeItem = storeItems.get(item.id)
return {
price_data: {
currency: 'usd',
product_data: {
name: storeItem.name
},
unit_amount: storeItem.priceInCents
},
quantity: item.quantity
}
}),
success_url: `${process.env.SERVER_URL}`,
cancel_url: `${process.env.SERVER_URL}cart`,
})
res.json({ url: session.url })
} catch (e) {
console.log(e)
res.status(500).json({ error: e.message })
}
});
So if I do the following code I can remove all cartItems from the user, however this happens regardless of if the payment was successful or not:
app.post('/api/createCheckoutSession', async (req, res) => {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
mode: 'payment',
line_items: req.body.items.map(item => {
const storeItem = storeItems.get(item.id)
return {
price_data: {
currency: 'usd',
product_data: {
name: storeItem.name
},
unit_amount: storeItem.priceInCents
},
quantity: item.quantity
}
}),
success_url: `${process.env.SERVER_URL}`,
cancel_url: `${process.env.SERVER_URL}cart`,
})
cartItem.remove({ user: req.body.user }, function (err) {
if (err) {
console.log(err)
}
})
res.json({ url: session.url })
} catch (e) {
console.log(e)
res.status(500).json({ error: e.message })
}
});
So looking through Stripe api documentation and googling the only thing that consistently comes up is success_url, which wasn't what I am looking for (at least I do not think it will fix what I am trying to do). So for the original code I tried to console.log(session) and found a payment_status: 'unpaid' and figured I could use this to do what I am trying to by the following:
app.post('/api/createCheckoutSession', async (req, res) => {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
mode: 'payment',
line_items: req.body.items.map(item => {
const storeItem = storeItems.get(item.id)
return {
price_data: {
currency: 'usd',
product_data: {
name: storeItem.name
},
unit_amount: storeItem.priceInCents
},
quantity: item.quantity
}
}),
success_url: `${process.env.SERVER_URL}`,
cancel_url: `${process.env.SERVER_URL}cart`,
})
console.log(session)
if (session.payment_status == 'paid') {
cartItem.remove({ user: req.body.user }, function (err) {
if (err) {
console.log(err)
}
})
}
res.json({ url: session.url })
} catch (e) {
console.log(e)
res.status(500).json({ error: e.message })
}
});
With the above code, the if statement does not work as the only time something gets logged to the console is when the session first gets created, so the if statement does not execute after the payment succeeds or gets canceled.
Another solution is for me to create a different success_url where it pops up and I have a react useEffect call the remove cart items function. And then it redirects again somewhere else, but as I alluded to earlier this just seems like a terrible solution when it feels like I am just missing something very simple to get this to work the way I have attempted to.
I'm going to restate what I think your goal is here so I am clear on what the answer is.
After user is sent to checkout you have 2 potential outcomes you want your app to handle:
Successful payment: Get money, get user their goods
Payment canceled: Empty user cart
The problem you are running into is that your code can only await the creation of the user session. At that point you should just redirect to the Checkout URL. At this point your code does not know whether the payment will go through or not because the user is just being redirected to the Checkout UI where they can make their payment.
The state of the user's payment is reported back to your app in 2 different ways.
Customized Success/Cancel URLS - While this doc focuses on capturing the Session ID in the Success URL, you can do the same thing with the Cancel URL. In this case which URL the user is sent to tells your system a Checkout was either successful or was canceled. The Session ID injected into the URL identifies which Checkout session it was.
Checkout Webhook events - This approach provides confirmation of success but requires waiting until the session is expired to confirm cancelation. Still, it is recommended that most integrations make use of Webhooks to monitor account activities.
So to circle back to your code, I would move the code that clears the cart to a function that responds to either the user being redirected to the cancel_url in your app or responds to the checkout.session.expired webhook event
I have followed the example in Display the Sign In With Google button to get a Google sign in button working in my Angular application:
<div id="g_id_onload"
class="mt-3"
data-client_id="XXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com"
data-login_uri="http://localhost:1337/login/google"
data-auto_prompt="false">
</div>
<div class="g_id_signin"
data-width="250"
data-type="standard"
data-size="large"
data-theme="outline"
data-text="continue_with"
data-shape="rectangular"
data-logo_alignment="center">
</div>
Once the user signs in, I verify and decode the JWT token provided by Google in my Express server using jsonwebtoken:
app.post('/login/google', express.urlencoded(), async(request, response, next) => {
try {
console.log(`${request.method} ${request.url} was called.`);
let token: string = request.body.credential;
let body: Response = await fetch('https://www.googleapis.com/oauth2/v1/certs', { method: 'GET', headers: { Accept: 'application/json' }});
let json: any = await body.json();
let certificates: string[] = Object.keys(json).map(key => json[key]);
let decoded: any;
let lastError: any;
certificates.every(certificate => {
try {
decoded = jwt.verify(token, certificate, { algorithms: ['RS256'], ignoreExpiration: false });
}
catch (error) {
lastError = error;
}
return !decoded;
});
if (!decoded)
throw lastError;
}
catch (error) {
next(error);
}
});
The problem is that the decoded token does not contain the user's gender or birthday information. How can I obtain this data?
I have just recently tried manually appending the https://www.googleapis.com/auth/user.birthday.read and https://www.googleapis.com/auth/user.gender.read scopes to my application's OAuth Consent Screen found at https://console.cloud.google.com/apis/credentials/consent/edit, but I don't see the user being prompted to provide this data to my application when it runs. I tried deleting permissions to my application from my account at accounts.google.com (under the Third-Party Access section) as well in hopes that it might prompt for these extra pieces of data. I am not sure at this point how to go about getting this extra data because I can't seem to find a good documentation piece on how to achieve this. Also, I wanted to add that my test account's Gender and Birthday information is set to be Private in https://myaccount.google.com/personal-info. I was wondering if its possible to fetch these private scopes somehow.
So, just to be clear, when I try to sign in I still only get the following prompt, which makes me believe that something is wrong and its not actually requesting the scope for birthday and gender from the user:
Confirm you want to sign in to [Application Name] with [User's Name].
To create your account, Google will share your name, email address,
and profile picture with [Application Name].
I also tried going on https://developers.google.com/oauthplayground/ and I pasted this in for Input your own scopes: https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/userinfo.profile,https://www.googleapis.com/auth/user.birthday.read,https://www.googleapis.com/auth/user.gender.read. I then hit the Authorize API button, logged in, granted access to these scopes (was prompted correctly on the playground), performed the token exchange, then I tried to List possible operations and under the People API, I called the get people endpoint, and modified the URI to https://people.googleapis.com/v1/people/me as per the documentation. This endpoint seems to work to fetch the data I need, but now I can't seem to wrap my head around what authorization parameters to use for this endpoint from the data I get back from the POST to my Express server. I have also tried enabling the People API from Enabled APIs & services.
You are using signin. Signin is open id connect and returns an id token. Id tokes contain very few claims. Gender is not one of them.
The only way to get access to the full user profile info is to go though the people api as you have mentioned.
You can use the try me to see it working and generate the sample for you.
<script src="https://apis.google.com/js/api.js"></script>
<script>
/**
* Sample JavaScript code for people.people.get
* See instructions for running APIs Explorer code samples locally:
* https://developers.google.com/explorer-help/code-samples#javascript
*/
function authenticate() {
return gapi.auth2.getAuthInstance()
.signIn({scope: "https://www.googleapis.com/auth/contacts https://www.googleapis.com/auth/contacts.readonly https://www.googleapis.com/auth/directory.readonly https://www.googleapis.com/auth/user.addresses.read https://www.googleapis.com/auth/user.birthday.read https://www.googleapis.com/auth/user.emails.read https://www.googleapis.com/auth/user.gender.read https://www.googleapis.com/auth/user.organization.read https://www.googleapis.com/auth/user.phonenumbers.read https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"})
.then(function() { console.log("Sign-in successful"); },
function(err) { console.error("Error signing in", err); });
}
function loadClient() {
gapi.client.setApiKey("YOUR_API_KEY");
return gapi.client.load("https://people.googleapis.com/$discovery/rest?version=v1")
.then(function() { console.log("GAPI client loaded for API"); },
function(err) { console.error("Error loading GAPI client for API", err); });
}
// Make sure the client is loaded and sign-in is complete before calling this method.
function execute() {
return gapi.client.people.people.get({
"resourceName": "people/me",
"personFields": "genders"
})
.then(function(response) {
// Handle the results here (response.result has the parsed body).
console.log("Response", response);
},
function(err) { console.error("Execute error", err); });
}
gapi.load("client:auth2", function() {
gapi.auth2.init({client_id: "YOUR_CLIENT_ID"});
});
</script>
<button onclick="authenticate().then(loadClient)">authorize and load</button>
<button onclick="execute()">execute</button>
The issue that you are then going to have is the above sample uses Oauth2 and not open id connect (signin) It needs an access token to work. If you check your code I belive that the signin does return an access token. Your job then is to feed the access token to the code above so that you dont have to go though the authorization process again.
So far i have not found anyone able to link the new signin system with the old oauth2 system. If you get it to work i would love to see it.
Html
To call this api you need an access_token. a google access token is not a jwt. it is not the id_token
GET https://people.googleapis.com/v1/people/me?personFields=genders&key=[YOUR_API_KEY] HTTP/1.1
Authorization: Bearer [YOUR_ACCESS_TOKEN]
Accept: application/json
I finally managed to get it working with the help of this guide.
I had to scrap the idea of using the Google sign in button because it does not seem to allow extended scopes such as birthday and gender (well, not if they're private anyways - if anyone finds a way of doing it with the sign in button, please post an answer). Luckily, their OAuth API does support extended scopes. As such, I've implemented my own Google sign in button using the googleapis package.
There are a few steps to this:
Use the googleapis package to generate a URI to present to the user that will ask them to consent to gender and birthday access.
For example:
app.get('/login/google/uri', async(request, response, next) => {
try {
console.log(`${request.method} ${request.url} was called.`);
let client = new google.auth.OAuth2(
'ClientID',
'ClientSecret',
`http://localhost:4200/login/google/redirect`
);
const scopes = [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/user.birthday.read',
'https://www.googleapis.com/auth/user.gender.read'
];
const authorizationUrl: string = client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
include_granted_scopes: false
});
response.status(200).send({ uri: authorizationUrl });
}
catch (error) {
next(error);
}
});
Ensure that http://localhost:4200/login/google/redirect (or whatever redirect URI you use) is part of your OAuth 2.0 Client ID Credential's Authorized redirect URIs in the console.
Google will redirect to your redirect URI (http://localhost:4200/login/google/redirect) with a query parameter named code. For example: http://localhost:4200/login/google/redirect?code=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&scope=email%20profile%20https:%2F%2Fwww.googleapis.com%2Fauth%2Fuser.gender.read%20https:%2F%2Fwww.googleapis.com%2Fauth%2Fuser.birthday.read%20https:%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20https:%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20openid&authuser=0&prompt=consent
Take the code (XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX) and exchange it for an access token.
For example:
let client = new google.auth.OAuth2(
'ClientID',
'ClientSecret',
`http://localhost:4200/login/google/redirect`
);
let code: string = request.params.code;
let { tokens } = await client.getToken(code);
console.log(tokens.access_token);
Use the access_token (it looks something like XXXX.XXXXXXXXXXXXXXXXXXXXXXXXXXX-XXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX) when making requests to the People API and set it in the Authorization header as the bearer token.
For example:
curl "https://people.googleapis.com/v1/people/me?key=XXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXX&personFields=genders,birthdays" -H "Authorization: Bearer XXXX.XXXXXXXXXXXXXXXXXXXXXXXXXXX-XXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
key is your API key from the console (you can create one and restrict it to the People API - if you don't see the People API as a restriction option you might need to enable it from the Enabled APIs and services tab). I'm sure there is a more API friendly way of making this request in the googleapis package that you can explore, but I just wanted to highlight how it works with curl.
The response you will see should be like this:
{
"resourceName": "people/XXXXXXXXXXXXXXXXXXXX",
"etag": "XXXXXXXXXXXXXXXXXXXXX",
"genders": [
{
"metadata": {
"primary": true,
"source": {
"type": "PROFILE",
"id": "XXXXXXXXXXXXXXXXXXXX"
}
},
"value": "male",
"formattedValue": "Male"
}
],
"birthdays": [
{
"metadata": {
"primary": true,
"source": {
"type": "ACCOUNT",
"id": "XXXXXXXXXXXXXXXXXXXX"
}
},
"date": {
"year": 1901,
"month": 1,
"day": 1
}
}
]
}
Edit: Just for completion, here is the API friendly way to do all of this.
First, generate this URI and redirect the user to it:
app.get('/login/google/uri', async(request, response, next) => {
try {
console.log(`${request.method} ${request.url} was called.`);
let client = new googleapis.Auth.OAuth2Client(
Globals.GoogleClientID,
Globals.GoogleClientSecret,
`${Globals.UIHost}/login`
);
const scopes = [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/user.birthday.read',
'https://www.googleapis.com/auth/user.gender.read'
];
const authorizationUrl: string = client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
include_granted_scopes: false
});
response.status(200).send({ uri: authorizationUrl });
}
catch (error) {
next(error);
}
});
Second, after the user has signed in and you get a code posted back to your redirect URI, parse the query param for the code and use it like how I am doing so in the following POST method on my server to get these extra user details for birthdays, genders, and emails:
app.post('/login/google', express.json(), async(request, response, next) => {
try {
console.log(`${request.method} ${request.url} was called.`);
let client = new googleapis.Auth.OAuth2Client(
Globals.GoogleClientID,
Globals.GoogleClientSecret,
`${Globals.UIHost}/login`
);
let code: string = request.body.code;
let { tokens } = await client.getToken(code);
let accessToken: string = tokens.access_token;
client.setCredentials({ access_token: accessToken });
let people = new googleapis.people_v1.People({});
let result = await people.people.get({
resourceName: 'people/me',
personFields: 'emailAddresses,birthdays,genders',
auth: client
});
console.log(result.data);
}
catch (error) {
next(error);
}
});
result.data should contain the information.
If you are using NestJS with typescript, this worked for me
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(configService: ConfigService) {
super({
clientID: configService.get('GOOGLE_CLIENT_ID'),
clientSecret: configService.get('GOOGLE_SECRET'),
callbackURL: configService.get('GOOGLE_REDIRECT_URL'),
scope: [
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/plus.login',
'https://www.googleapis.com/auth/user.birthday.read',
'https://www.googleapis.com/auth/user.phonenumbers.read',
'https://www.googleapis.com/auth/user.gender.read',
],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: any,
done: VerifyCallback,
): Promise<any> {
const { name, emails, photos, sub, birthday, phoneNumber, gender } =
profile;
const user = {
sub,
email: emails[0].value,
firstName: name.givenName,
lastName: name.familyName,
picture: photos[0].value,
dob: birthday,
phoneNumber,
gender,
refreshToken,
accessToken,
};
done(null, user);
}
}
Then add GoogleStrategy to your provider. Of course, don't forget your keys in your .env file.
I have this:
const stripe = require('stripe')('sk_test', {
stripeAccount: 'acct_...'
});
const paymentIntent = await stripe.paymentIntents.create({
amount: 1900,
currency: 'cad',
customer: 'cus_...',
// confirm: true,
}, {
stripeAccount: 'acct_...',
});
console.log(paymentIntent)
so then I go to run this paymentIntent, and it works, but doesn't actaully charge the customer because it says that it has no payment method on file. So then I take this customers id, and look at my stripe dashboard, and it shows the payment method there, and the method matches with the id. so now I believe I am doing something wrong creating the paymentIntent, but the payment is going through, just not confirmed because it says no payment method attached! So why is this not working?
error: UnhandledPromiseRejectionWarning: Error: You cannot confirm this PaymentIntent because it's missing a payment method. You can either update the PaymentIntent with a payment method and then confirm it again, or confirm it again directly with a payment method.
PaymentIntent requires a Payment Method Object such as;
payment_method_types: [card],
PaymentIntent object
const {
error: backendError,
clientSecret,
paymentIntentId,
transfer_group,
} = await fetch('/create-payment-intent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
paymentMethodType: 'card',
currency: 'cad',
customerEmail: billingDetails.email,
description: 'Basket_Order_Id',
}),
}).then((r) => r.json());
and when you created the paymentintent on your backend you should return
app.post('/create-payment-intent', async (req, res) => {
const {paymentMethodType, currency, customerEmail, description,
suppliers} =
req.body;
console.log('paymentIntent Create requ body', req.body);
req.session.suppliers = suppliers;
suppliersArray = suppliers;
const idEmpotency = nanoid();
// Each payment method type has support for different currencies. In order
to
// support many payment method types and several currencies, this server
// endpoint accepts both the payment method type and the currency as
// parameters
//
// Some example payment method types include `card`, `ideal`, and
`alipay`.
const params = {
payment_method_types: [paymentMethodType], 'CARD'
amount: 20000,
currency: currency,
description: description,
receipt_email: customerEmail,
statement_descriptor_suffix: 'Your Bank Descriptor on Customer Account',
transfer_group: idEmpotency,
// confirm: true,
// capture_method: 'manual',
};
try {
const paymentIntent = await stripe.paymentIntents.create(params);
// Send publishable key and PaymentIntent details to client
console.log('paymentIntent', paymentIntent);
res.send({
clientSecret: paymentIntent.client_secret, - SENDING YOUR CLIENTSECRET
paymentIntentId: paymentIntent.id,
transfer_group: paymentIntent.transfer_group,
});
} catch (e) {
return res.status(400).send({
error: {
message: e.message,
},
});
}
});
client_secret and use it on your front-end
const {error: stripeError, paymentIntent} = await stripe.confirmCardPayment(
clientSecret, USE YOUR CLIENT SECRET THAT RETURNED FROM YOUR BACKEND FROM PAYMENT INTENT OBJECT
{
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
name: 'Michael',
},
},
}
);
Before confirming the client_secret that returned from payment_intent you can not succesfully confirm the payment.
You can use stripe elements to start with their own card payment component.
I recommend you to check here https://stripe.com/docs/payments/quickstart, you will get more idea...
What I want to do:
I use firebase so as soon as someone purchases a Product over stripe I want to write that on the document of the user that bought it.
I want to use a stripe webhook for that. This webhook looks like this:
It essentially makes a https request to firebase functions as soon as the payment went trough. It triggers a https function that should write to the user's document that he has purchased the product. But to do that the webhook needs to contain the firebase user uid of the user that bought the product.
But to pass the user uid to the webhook I need to pass it to the stripe.checkout.sessions.create() function that looks like this:
exports.createCheckoutSession = functions.https.onCall(async(data, context) => {
const YOUR_DOMAIN = 'http://localhost:4242';
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [{
price_data: {
currency: 'usd',
product_data: {
name: 'Stubborn Attachments',
images: ['https://i.imgur.com/EHyR2nP.png'],
},
unit_amount: 2000,
},
quantity: 1,
}, ],
mode: 'payment',
success_url: `${YOUR_DOMAIN}/success.html`,
cancel_url: `${YOUR_DOMAIN}/cancel.html`,
});
console.log(session.id)
return session.id
})
(the user calls this cloud function to get a checkout session returned)
But how can I pass the user uid (context.user.uid) in so I can later send it with the webhook.
I hope it is clear what I want to do if not please ask. Thank you for your time.
You can add Firebase UID of user or any other information in metadata of stripe checkout session like this:
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
// line items and other stuff
metadata: {firebaseUID: uid, product_id: "ProductID"},
});
You can read them in the checkout session completed webhook or any success webhook.