DEV Community

Ahmed Ben Bettaieb
Ahmed Ben Bettaieb

Posted on

Create custom connect and transfer money stripe for non registered countries

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;`
Enter fullscreen mode Exit fullscreen mode

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;`

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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");
    }
  }
);
Enter fullscreen mode Exit fullscreen mode

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" });
    }
  }
);
Enter fullscreen mode Exit fullscreen mode

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 });
    }
  }
);
Enter fullscreen mode Exit fullscreen mode

test this route with this json file :

{
  "accountType": "bank",
  "accountDetails": {
    "accountNumber": "EE382200221020145685"
  }
}
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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 :

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

Top comments (0)