Create custom connect and transfer money stripe for non registered countries
Ahmed Ben Bettaieb
Posted on June 15, 2023
Hello,
Through this post I will show how to create a stripe account and make a connect , and transfer money to the connect using node js (express with typescript) .
Assuming that your node js project is setup.
first of all you need to create a stripe account (in estonia) :
https://www.youtube.com/watch?v=vEbD8SQg5So&ab_channel=DistroTechnologies
follow this video .
after creating the stripe account you will get your publishable key and secret key , copy them and put them in .env file .
then run this command :
npm install stripe @types/stripe
then make a user model (mongoose) :
`import { Schema, Document, model } from 'mongoose';
// Define the User interface
interface IUser extends Document {
name: string;
email: string;
birthDate: Date;
accountId: string;
password: string;
}
// Define the User schema
const UserSchema = new Schema<IUser>({
name: { type: String, required: true },
email: { type: String, required: true },
birthDate: { type: Date, required: true },
accountId: { type: String, required: true },
password: { type: String, required: true },
});
// Create and export the User model
const User = model<IUser>('User', UserSchema);
export default User;`
and make sure to make an authMiddleware to verify that the user is authenticated:
`import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import dotenv from "dotenv";
dotenv.config();
const authMiddleware = (
req: Request,
res: Response,
next: NextFunction
) => {
try {
const token = req.headers["authorization"]?.split(" ")[1];
if (!token && !process.env.JWT_SECRET) {
// Token does not exist or JWT_SECRET is not set in the environment variables
console.log("Token does not exist");
return res.status(401).send({
message: "Authentication failed!",
success: false,
});
} else {
jwt.verify(
token as string,
process.env.JWT_SECRET as string,
(err, decoded: any) => {
// Verifying the token
console.log("Verifying token");
if (err) {
// Error occurred during token verification
console.log(err);
return res.status(401).send({
message: "JWT verification failed!",
success: false,
});
} else {
// Token is valid, extracting the user ID from the decoded token and attaching it to the request body
(req.body as any).userId = (decoded as any)._id;
next();
}
}
);
}
} catch (error: any) {
// Catching any other errors that might occur
console.log(error.message);
return res.status(401).send({
message: "Authentication failed",
success: false,
});
}
};
export default authMiddleware;`
then create a folder named routes and under this folder we will create a file named paymentRoute.ts, first of all :
`
//we import the stripe secret key and the public key from .env
const secret: string | undefined = process.env.STRIPE_SECRETKEY;
const publicKey: string | undefined = process.env.STRIPE_PUBLISHABLEKEY;
if (!secret) {
throw new Error("Stripe secret key not found in environment variables.");
}
// Creating a new instance of the Stripe API using the secret key
const stripe = new Stripe(secret, {
apiVersion: "2022-11-15",
});
after creating a new instance of stripe ,we pass to creating the connect ,
first we create the custom connect (assuming that the user is already defined ):
router.post(
"/create-stripe-account",
authMiddleware,
async (req: Request, res: Response) => {
try {
const user = await User.findOne({ _id: req.body.userId });
if (user) {
if (!user.accountId) {
// Creating a custom Stripe account for the user
const accountParams: Stripe.AccountCreateParams = {
country: "EE",
type: "custom",
business_type: "individual",
email: user.email,
capabilities: {
card_payments: {
requested: true,
},
transfers: {
requested: true,
},
},
};
const account = await stripe.accounts.create(accountParams);
// Saving the account ID to the user document
user.accountId = account.id;
await user.save();
res.status(200).json({ data: account, success: true });
} else {
res
.status(200)
.json({ message: "You already have an account", success: false });
}
} else {
res.status(404).json({ message: "User not found" });
}
} catch (error) {
res.status(200).json("Something went wrong");
}
}
);
if you access to your stripe account ,you will find a new connect but it is restricted .
so , we must update the account using this route :
router.put(
"/update-stripe-account",
authMiddleware,
async (req: Request, res: Response) => {
try {
const user = await User.findOne({ _id: req.body.userId });
if (user) {
const { firstName, lastName, email, accountId } = user;
const birthDateString = user.birthDate; // Assuming `user.birthDate` is defined
if (birthDateString) {
const dateObj = new Date(birthDateString);
const day = dateObj.getDate();
const month = dateObj.getMonth() + 1; // Month is zero-based, so we add 1 to get the correct month
const year = dateObj.getFullYear();
const { city, line, postalCode, state, ipAddress, phone ,url,} = req.body;
const date = Math.floor(Date.now() / 1000);
const stripeAccount = await stripe.accounts.update(accountId, {
business_type: "individual",
settings: {
payouts: {
schedule: {
interval: "manual" as any,
},
},
},
email,
individual: {
first_name: firstName,
last_name: lastName,
email: email,
maiden_name: firstName + " " + lastName,
phone: phone,
address: {
city,
line1: line,
line2: line,
postal_code: postalCode,
state,
},
dob: {
day,
month,
year,
},
},
business_profile: {
mcc: "7372",
url: url,
product_description:
"Description of the business's products or services",
},
tos_acceptance: {
date: date,
ip: ipAddress,
},
});
res.status(200).send({
success: true,
data: stripeAccount,
message: "account updated successfully",
});
} else {
res.status(404).send({
message:
"You should update your profile and add the missing information",
success: false,
});
}
}
} catch (error) {
console.log(error);
res.status(500).json({ message: "something went wrong" });
}
}
);
MCC stands for Merchant Category Code. It is a four-digit code used to categorize businesses based on the products or services they provide. you can find mored details in this doc https://stripe.com/docs/connect/setting-mcc
now we should add a payout method , this is the route for adding payout method :
router.post(
"/add-payout-method",
authMiddleware,
async (req: Request, res: Response) => {
try {
const user = await User.findOne({ _id: req.body.userId });
if (user) {
const accountId = user.accountId;
const { accountType, accountDetails } = req.body;
const account_holder_name = `${user.firstName} ${user.lastName}`;
// Create a bank account or debit card token
let externalAccountToken;
if (accountType === "bank") {
externalAccountToken = await stripe.tokens.create({
bank_account: {
country: "EE",
currency: "eur",
account_holder_name: account_holder_name,
account_holder_type: "individual",
account_number: accountDetails.accountNumber,
},
});
} else if (accountType === "card") {
externalAccountToken = await stripe.tokens.create({
card: {
number: accountDetails.cardNumber,
exp_month: accountDetails.expMonth,
exp_year: accountDetails.expYear,
cvc: accountDetails.cvc,
currency: "eur",
},
});
const externalAccount = await stripe.accounts.createExternalAccount(
accountId,
{
external_account: externalAccountToken.id,
}
);
await stripe.accounts.update(accountId, {
capabilities: {
transfers: { requested: true },
},
});
} else {
return res.status(400).json({ message: "Invalid account type" });
}
// Associate the bank account or debit card with the Stripe account
await stripe.accounts.createExternalAccount(accountId, {
external_account: externalAccountToken.id,
});
res
.status(200)
.json({ success: true, message: "Payout method added successfully" });
} else {
res.status(404).json({ message: "User not found" });
}
} catch (error) {
res.status(500).json({ error });
}
}
);
test this route with this json file :
{
"accountType": "bank",
"accountDetails": {
"accountNumber": "EE382200221020145685"
}
}
after adding payout method now the custom connect is ready to receive money , this is the route for making transfer:
router.post("/transaction/:contractId", authMiddleware, async (req, res) => {
try {
const user = await User.findOne({ _id: req.body.userId });
const contract = await Contract.findOne({
_id: req.params.contractId,
});
if (user && contract) {
const paymentIntent = await stripe.paymentIntents.create(
{
amount: contract.price,
currency: "eur",
automatic_payment_methods: {
enabled: true,
},
},
{
stripeAccount: user.accountId,
}
);
console.log("user and contract found");
const freelancerTransfer = await stripe.transfers.create({
amount: contract.price*99,
currency: "eur",
destination: user.accountId,
});
user.unSeenNotifications.push(
"your transaction is created successfully"
);
user.save();
res.status(200).json({
message: "Transfer created successfully",
});
} else {
res.status(404).json({ message: "User or contract not found" });
}
} catch (error) {
res.status(500).json({ error });
}
});
for my project , the amount was taken from the contract price .
after making the the last route and test it with postman ,and we can access to our stripe dashboard :
we see that the connect has received his money and he is ready to payout.
Note:I'm speaking for test mode
That's it , I wish it was helpful !
I'm waiting for your feedbacks
Posted on June 15, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.