无法将金额转移到Connect Stripe帐户

时间:2020-05-29 21:58:18

标签: node.js stripe-payments

背景:

我要做的是建立一个客户可以从其那里获得卖方服务的市场。该项目是一个确切的MERN堆栈旅行应用程序。我想让客户在希望获得服务(例如酒店客房)时支付平台(与Stripe连接的我的网站)。客户在指定的时间停留在酒店,当他结帐时,平台会保留一些客户的申请费,然后将其余部分转移给服务提供商(在这种情况下为酒店)。

目前的努力:

我使用STRIPE CONNECT实现了所需的功能。

Note:你们不需要看到下面的所有代码,只需标题和描述就可以使您了解我已经做了什么以及我想问什么,但是请阅读问题部分)

我在卖家登录我的网站时为卖家创建了Connect account

创建连接帐户

const express = require("express");
const router = express.Router();
  router.post("/createAccount", async (req, res) => {
  const { name, email } = req.body; //Data Passed from the FrontEnd
  stripe.accounts.create(
    {
      type: "custom",
      country: "US",
      email: email,
      requested_capabilities: ["card_payments", "transfers"],
    },
    function (err, account) {
      res.json({ account: account });
    }
  );
});

登录Seller Portal后,当卖方提供其余的所需详细信息(包括银行帐户)时,我将创建一个bank_account,更新已创建的Connect Account并链接bank_accountConnect Account(希望这可以说是合乎情理的)

创建银行帐户

  router.post("/createBankAccount", async (req, res) => {
  const { account_holder_name, routing_number, account_number } = req.body;
  stripe.tokens.create(
    {
      bank_account: {
        country: "US",
        currency: "USD",
        account_holder_name,
        account_holder_type: "individual",
        routing_number,
        account_number,
      },
    },
    function (err, token) {
      res.send(token);
    }
  );
});

更新帐户:

  router.post("/updateAccount", async (req, res) => {
      const {
        AccountID,
        Day,
        Month,
        Year,
        first_name,
        last_name,
        email,
        BankAccountID,
      } = req.body;

      const FrontFilePath = fs.readFileSync("PathToFileHere");
      const FrontPhotoIDUpload = await stripe.files.create({
        file: {
          data: FrontFilePath,
          name: "FrontPhotoID.jpg",
          type: "application.octet-stream",
        },
        purpose: "identity_document",
      });

      const BackFilePath = fs.readFileSync("PathToFileHere");
      const BackPhotoIDUpload = await stripe.files.create({
        file: {
          data: BackFilePath,
          name: "BackPhotoID.jpg",
          type: "application.octet-stream",
        },
        purpose: "identity_document",
      });

      stripe.accounts.update(
        AccountID,
        {
          business_type: "individual",
          individual: {
            dob: { day: Day, month: Month, year: Year },
            first_name: first_name,
            last_name: last_name,
            id_number: "006-20-8311",
            phone: "605-628-6049",
            address: {
              city: "Half Way",
              line1: "2467  Twin House Lane",
              postal_code: "65663",
              state: "MO",
            },
            email,
            ssn_last_4: "8311",
            verification: {
              document: {
                front: FrontPhotoIDUpload.id,
                back: BackPhotoIDUpload.id,
              },
            },
          },
          business_profile: {
            mcc: "4722",
            url: "http://www.baoisne.com",
          },
          tos_acceptance: {
            date: Math.floor(Date.now() / 1000),
            ip: req.connection.remoteAddress, 
          },
        },
        function (err, account) {
          console.log(err);
          console.log(account);
        }
      );
     //Connect External Account
      stripe.accounts.createExternalAccount(
        AccountID,
        {
          external_account: BankAccountID,
        },
        function (err, bankAccount) {
          console.log(err);
          res.send(bankAccount);
        }
      );
    });

然后,当客户提供其帐户详细信息时,我会向客户收费,保留一些钱作为申请费,然后将其余款项移至Service Providers Connect帐户。

收费客户

  router.post("/charge", async (req, res) => {
  const { TokenID, CustomerID, Amount, AccountID } = req.body;
  let PaymentAmount = Amount * 100;
  let application_fee_amount = 400;
  try {
    const payment = await stripe.paymentIntents.create({
      amount: PaymentAmount,
      currency: "USD",
      description: "We did it boss",
      payment_method_data: {
        type: "card",
        card: {
          token: TokenID,
        },
      },
      receipt_email: "abdullahabid427@gmail.com",
      customer: CustomerID,
      application_fee_amount,
      transfer_data: {
      destination: AccountID,
      },
      confirm: true,
    });
    return res.status(200).json({
      confirm: "Payment Succeeded",
    });
  } catch (error) {
    console.log(error);
    return res.status(400).json({
      message: error.message,
    });
  }
});

通过执行上述过程,将创建一个关联帐户,并将金额移入关联帐户。

问题

上述过程虽然可以正常运行,但是在向客户收费之后,我希望将金额直接移到关联服务提供商帐户中,我希望客户付款给平台,并且在服务提供商提供其服务后,平台向服务提供商付款,我考虑过删除

  application_fee_amount,
  transfer_data: {
  destination: AccountID,
  }

ChargeStripe.paymentIntents.create端点中的上述参数,在服务提供商完成其服务后,我使用Stripe Transfer API转移了金额

router.post("/transfer", async (req, res) => {
  try {
    console.log("TRANSFER=");
    const { AccountID, amount } = req.body;
    const transfer = await stripe.transfers.create({
      amount,
      currency: "USD",
      destination: AccountID,
    });
    res.send(transfer);
  } catch (error) {
    res.send(error);
  }
});

这里的问题是转账端点返回“ 您的目标帐户需要至少启用以下功能之一:转账,legacy_payments ”,我已经在Stripe仪表板和“功能”部分的Card_PaymentTransfers都设置为“有效”,并且“付款”和“付款”都已启用,并且连接帐户的状态为“完成”

因此,如果有人能指出正确的方向,我将不胜感激,干杯:)

2 个答案:

答案 0 :(得分:3)

好的-我们同意Stripe可以正常工作。您收到的错误消息是因为您从付款意图创建功能中删除了目标帐户ID。这就是问题所在,在您的收费客户标题下。

让我们看一下:(简化版)

const payment = await stripe.paymentIntents.create({
  amount: PaymentAmount,
  currency: "USD",
  ...
  customer: CustomerID,
  application_fee_amount,
  transfer_data: {
    destination: AccountID,
  },
  confirm: true,
});

最后一个属性confirm: true等效于在同一呼叫中创建并确认付款意向。默认值为false -使用该值,新创建的付款意图的状态将为requires_confirmation。准备好后,您可以按照以下方式确认付款意向:

const confirmedPayment = await stripe.paymentIntents.confirm(
    'payment_intent_id',
    {payment_method: 'card'},
    function(err, paymentIntent) {

    }
});

关于出现问题的一些常规评论

当付款人在线上为某些商品付款时,应用程序开发人员有责任实施逻辑,根据该逻辑来发送和接收金钱和商品:可以预付,后付或部分支付。没有逻辑是万无一失的。一般而言,如果我们担心客户会利用我们的付款政策,则可以要求所有付款方预先支付所有费用,并包括公平退款政策。在这种情况下,Stripe支持退款付款意向,但更重要的是:它可以跟踪付款状态。

创建付款意图但未确认时,其状态为requires_confirmation。那里没什么大不了的。但是在确认付款意向后,状态将为processing-可能需要几天的时间。您可以随时决定取消付款。但是,如果一切顺利,状态将更改为succeeded,这意味着资金已在目标帐户中。但是,如果由于某种原因付款失败,状态将返回到requires_payment_method。即使在这种情况下,也无需创建新的付款或转账对象。您可以随时调用stripe.retrievePaymentIntent(clientSecret)并查看状态,以检索付款意向。但是我认为,使用配置为接收状态更改事件的Webhook监视状态更改要容易得多。即使状态更改后没有立即采取任何措施,我们也可以将状态存储在可用的数据库中。

根据经验,我已经看到付款失败的普遍性。这并不意味着双方都有欺诈行为,但这确实意味着该应用应该准备好应对这两种情况。要添加到webhook配置的事件是payment_intent.succeededpayment_intent.payment_failed。这些事件的处理方式针对每个应用程序。

答案 1 :(得分:-1)

创建一个Webhook(条带配置),其中包括:

  • 发送到网络挂钩的事件:在这种情况下为customer.createdcustomer.source.createdcustomer.source.updated
  • URL =事件到达时处理事件的路径

因此,您需要先将待处理的付款存储在数据库中。然后在webhook中,在数据库中找到它并完成传输。