Create custom connect and transfer money stripe for non registered countries

ahmedbenbettaieb

Ahmed Ben Bettaieb

Posted on June 15, 2023

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

πŸ’– πŸ’ͺ πŸ™… 🚩
ahmedbenbettaieb
Ahmed Ben Bettaieb

Posted on June 15, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related