Search code examples
pythonstripe-paymentspayment

Python Stripe - Create Connect & Customer users - charge card and transfer money into a bank account


I have two types of users in Stripe:

  • End Customers (that pay and receive a product/service);
  • Connect Users (that will have Connect Accounts and will be providing products/service);

Let's assume in an example below user is a model instance(Django):

# Connect Account
instance = stripe.Account.create(
    type="custom",
    country="PL",
    email=user.email,
    default_currency="pln",
    business_type="individual",
    requested_capabilities=["card_payments", "transfers",],
    individual={
        "first_name": user.first_name.strip(),
        "last_name": user.last_name.strip(),
        "email": user.email,
        "dob": {
            "day": user.profile.date_of_birth.day,
            "month": user.profile.date_of_birth.month,
            "year": user.profile.date_of_birth.year,
        },
        "phone": user.profile.phone_number,
        "address": {
            "city": user.city.strip(),
            "postal_code": user.profile.postal_code.strip(),
            "country": "PL",
            "line1": user.profile.address.strip(),
        },
    },
)
# Customer Account
instance = stripe.Customer.create(
    email=user.email,
    description="desc",
    name=user.get_full_name.strip(),
    phone=user.profile.phone_number.strip(),
)

Those two API calls work properly. Then I'm able to retrieve the unique Stripe ID for new user:

stripe_customer_id = instance.get("id")

For stripe.Account type, I need to provide an identity verification: front and back of ID Card (as an example):

document_images_data = [
   "data:image/png;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",
   "data:image/png;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",
]

_front = stripe.File.create(
    purpose="identity_document",
    file=Base64ImageField().to_internal_value(base64_data=document_images_data[0]),
    stripe_account=stripe_customer_id,
)

_back = stripe.File.create(
    purpose="identity_document",
    file=Base64ImageField().to_internal_value(base64_data=document_images_data[1]),
    stripe_account=stripe_customer_id,
)

individual = {
    "verification": {
        "document": {"front": _front.get("id"), "back": _back.get("id")},
        "additional_document": {"front": _front.get("id"), "back": _back.get("id")},
    }
}

Then I need to attach uploaded files to stripe.Account instance:

stripe.Account.modify(stripe_customer_id, individual=individual)

And accept Stripe terms through API:

stripe.Account.modify(stripe_customer_id, tos_acceptance={"date": int(time.time()), "ip": user.user_ip,})

After a successful creation of stripe.Account instance, I can see on the Stripe's dashboard, there are two things left to be filled, so that the account can be completely verified:

  • Industry
  • Business website

Is there a way to somehow fill these data in API call ? What is more, I'm trying to create an individual type of account, so why does it require those two things to be filled as well ?

From now, I have two users and I'd like to add a Card to Customer Account and Bank Account to Connect Account.

In both situations I'm using a card/bank account details provided by a token, like this (Stripe test card/bank account):

stripe.Customer.create_source(stripe_customer_id, source="tok_amex")  # example token card details

stripe.Account.create_external_account(
    stripe_customer_id,
    external_account="btok_1H7J0nHksFGR44vruZzOpnnD",  # example token bank account details
)

And now, let's assume I want to charge a Customer card and transfer those money into Connect Account.

As I understood from documentation, it should be:

stripe.Charge.create(
    amount=10000,
    currency="pln",
    customer=stripe_customer_id,  # stripe.Customer unique Stripe ID
    description="some desc",
)

This operation also works as I can see the result on Stripe's Dashboard.

Now I want to transfer those money into a Connect Account:

stripe.Transfer.create(amount=10000, currency="pln", destination=connect_account_stripe_id)

Even though I added some credits to stripe.Customer balance I got:

You have insufficient funds in your Stripe account. One likely reason you have insufficient funds is that your funds are automatically being paid out

How to enable manual transfers? And the most important question - Is it a proper approach or the entire process should be handled in different way? Thanks in advance!


Solution

  • When you process a payment against the customer(stripe.Charge.create), this adds funds to your platform account's balance. Funds are initially pending for some time(https://stripe.com/docs/payouts#standard-payout-timing).

    Calling /v1/transfers (stripe.Transfers.create) attempts to take funds from your available balance and transfer them to the destination Stripe account. It will fail if you have no available balance as is most likely the case here.

    If your platform is on automatic daily payouts(the default setting), you will never actually have an available balance. That's because Stripe will move the funds directly from your pending balance into your payouts so you can get that money as quickly as possible, which is what most accounts would want(as a Connect platform with this funds flow, you are a bit more advanced).

    If you want to do this kind of transfer, I'd suggest the following, in order:

    • if the 'delay' between the payment by the customer and the payout to the connected account(how long it takes for the service to be provided for example) is less than 7 days, you could just use Destination Charges(https://stripe.com/docs/connect/destination-charges) with auth-and-capture(https://stripe.com/docs/payments/capture-later). It's much less work than doing separate charges and transfer and you don't have to think about balances as much.
    • otherwise, you will need to either :
      • use source_transaction when calling stripe.Transfer.create : https://stripe.com/docs/connect/charges-transfers#transfer-availability . That lets the transfer succeed right now, and the funds from the incoming charge will move to the connected account when they're available.
      • or, set your platform to manual payouts : https://dashboard.stripe.com/settings/payouts Over time, you will accumulate an available balance from your pay-ins. You can then just make transfers directly from that balance, like the API call you shared in the question. In test mode you can use a special test card to simulate having available balance without waiting days.

    Why I am getting 'insufficient funds' when trying Stripe Transfers even though I added TEST mode funds in my Account?

    And the most important question - Is it a proper approach or the entire process should be handled in different way?

    This model(having a marketplace where each user of the platform has a Stripe Customer and a Stripe Account associated with them that you tie together) is fairly normal, but it's tricky to build. Similarly, really you're just building a standard "separate charges and transfers" integration : https://stripe.com/docs/connect/charges#separate-charges-transfers But you're building one of the most complex things on Stripe (Custom Connect + separate Charges and Transfers) so it's a bit of an undertaking.

    I would try to simplify it as much as possible by using Destination charges unless you absolutely can't, and using Express or Stripe's hosted onboarding for Custom accounts (https://stripe.com/docs/connect/connect-onboarding) unless you really need to whitelabel this.

    Also you should be using PaymentIntents(https://stripe.com/docs/payments/accept-a-payment) for the pay-in part, not Charge.create and Tokens as those don't support 3D Secure which is important in Europe, but it's maybe ok to use them just to test out the Connect side of things.