iOS/Firebase - Stripe Connect Account Verification - stripe-payments

I've created Connected Accounts using Stripe Connect. The account is created, however, it is restricted due to:
INFORMATION NEEDED
Identity document
Owner's additional document
After messing around, I realised if I just go back and mess around with the settings I am then prompted to verify the account. Is there a way in which I can always demand verification when users sign up? I've looked at the documents, but they have not been much help to me.
This is my code:
exports.createConnectAccount = functions.https.onRequest((req, res) => {
var data = req.body
var email = data.email
var response = {}
stripe.accounts.create(
{
object: 'account',
type: 'express',
country: 'GB',
business_type: 'individual',
email: email,
capabilities: {
card_payments: {
requested: true,
},
transfers: {
requested: true,
},
},
},
function(err, account) {
if (err) {
console.log("Couldn't create stripe account: " + err)
return res.send(err)
}
console.log("ACCOUNT: " + account.id)
response.body = {success: account.id}
return res.send(response)
return admin.firestore().collection('vendors').doc(user.uid).set({account_id: account.id});
}
);
});

Your code specifically creates Express accounts with the card_payments and transfers capabilities. In order for those capabilities to be active you'd need your user to provide additional information.
You can use the Account Links API to redirect your users to a Stripe hosted onboarding form which will collect all of this for you.

Related

disable verification code being sent on email verification on cognito

userPool.signUp(
userData.email,
userData.password,
attributeList,
[],
async (err, result) => {
if (err) {
failure(new HttpException(500, err.message));
}
let myCredentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: process.env.USER_POOL_ID!,
});
new AWS.Config({
credentials: myCredentials,
region: process.env.AWS_REGION,
});
let cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider();
cognitoIdentityServiceProvider.adminConfirmSignUp(
{
UserPoolId: process.env.USER_POOL_ID!,
Username: userData.email,
},
function (err, _data) {
if (err) {
failure(err);
}
cognitoIdentityServiceProvider.adminUpdateUserAttributes(
{
UserPoolId: process.env.USER_POOL_ID!,
Username: userData.email,
UserAttributes: [
{
Name: 'email_verified',
Value: 'true',
},
],
},
() => {
console.log('done');
}
);
}
);
if (result) {
const cognitoUser = result.user;
success({ user: cognitoUser, sub: result.userSub });
} else {
failure(
new HttpException(
500,
'Authentication server error, please try again later'
)
);
}
}
);
This is the code by which I am signing up a user and auto verify that user on cognito.
Problem being I have 2 different set of user roles, and one user i am auto confirming but for the other i want them to manually confirm, using the code that is sent by cognito.
Now for the one type of user for which the auto_confirmation is done the email and cofirm user is working perfectly, with one caveat.
The code for verification is being sent even if it's auto verified by admincognito.
How can i disable this on this particualr set of code, so that the other user role can confirm with the code that is being sent via email
Cognito isn't really configurable in this regard. Your escape hatch in this case is the lightly documented custom email sender lambda trigger. Perform whatever checks you want before sending the email through SES (or not).
I solved this issue by creating a pre-signup lambda trigger. And passing in the role to it as a user attribute and then auto verifying email and user based on the role.

How to edit event in google calendar created by service account

I am emailing an HTML link of a Google calendar event generated by Google API to users but they are unable to edit the event, they can only view it when they click the link. I am creating this event with a service account and sharing with other users.
How can I ensure these events are editable in the user's calendar?
This is a link to the code I am using:
const { google } = require('googleapis');
// Provide the required configuration
const CREDENTIALS = JSON.parse(process.env.CREDENTIALS);
const calendarId = process.env.CALENDAR_ID;
// Google calendar API settings
const SCOPES = ['https://www.googleapis.com/auth/calendar.events'];
const calendar = google.calendar({version : "v3"});
const auth = new google.auth.JWT(
CREDENTIALS.client_email,
null,
CREDENTIALS.private_key,
SCOPES,
'email_used_to_configure_the_service_account#gmail.com'
);
auth.authorize(function (err, tokens) {
if (err) {
console.log(err);
return;
} else {
console.log("Successfully connected!");
}
});
//fetching the even object from the db to get evnt.name and co
const saveEvent = {
summary: event.name,
location: event.room.host.location,
description: event.extra,
colorId: 3,
start: {
dateTime: event.startDate,
timeZone: 'Africa/Lagos',
},
end: {
dateTime: event.endDate,
timeZone: 'Africa/Lagos',
},
organizer: {
email: 'email_used_to_configure_the_service_account#gmail.com',
displayName: 'display name',
self: true
},
attendees: [{ email: 'email of recepient of event' }]
//visibility: 'public'
}
async function generateLink(){
try{
const val = await calendar.events.insert({ auth: auth, calendarId: calendarId, resource: saveEvent, sendNotifications: true });
if(val.status === 200 && val.statusText === 'OK'){
console.log('CREATED', val);
return val.data.htmlLink;
}
return console.log('NOT CREATED')
} catch(error){
console.log(`Error ${error}`);
return;
}
}
const link = await generateLink();
let mailData = {
name: user.name ? user.name : user.firstname,
token: `${config.get('platform.url')}/event-accepted/${invite.token}`,
coverImage: event.gallery.link,
eventName: event.name,
hostName: host.name? host.name : host.firstname,
venue: event.venue,
date: event.startDate,
time: event.startDate,
attendees: '',
ticketNo: '',
cost: event.amount,
action: link // htmlLink that takes you to the calendar where user can edit event.
}
mail.sendTemplate({
template: 'acceptEventEmail',
to: u.email.value,
context: mailData
});
Current Behaviour
Expected behaviour
P.S: Code has been added to the question
YOu created the event using a Service account, think of a service account as a dummy user. When it created the event it became the owner / organizer of the event and there for only the service account can make changes to it.
You either need the service account to update it and set someone else as organizer
"organizer": {
"email": "me#gmail.com",
"displayName": "L P",
"self": true
},
Service accounts cannot invite attendees without Domain-Wide Delegation of Authority
In enterprise applications you may want to programmatically access users data without any manual authorization on their part. In Google Workspace domains, the domain administrator can grant to third party applications domain-wide access to its users' data—this is referred as domain-wide delegation of authority. To delegate authority this way, domain administrators can use service accounts with OAuth 2.0.

Create Stripe Connect Customer with Payment Method on Platform Account

I am following the docs here about enabling other business to accept payments directly.
I have successfully created a connected account and accepted a payment from a customer on the connected account, but I have not yet been able to save that customers payment information for future payments.
All across the documentation, especially here it assumes you already created a customer on the platform account WITH a payment method, before you should try and clone the payment method to the connected accounts.
I cannot for the life of me figure out how to create a customer with payment information on the platform account before I clone them on the connected account.
As per the documentation, I started here on the client side where the accountID is the ID of the connected account:
const [stripePromise, setstripePromise] = useState(() =>
loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, {
stripeAccount: uid.accountID,
})
);
My Stripe Elements are created with the stripePromise.
When trying to save the card details I do this on the client side:
This is where I believe my mistake is I am using the connected accounts credentials while trying to create a platform payment method.
const handleRememberMe = async () => {
const { token } = await stripe.createToken(
elements.getElement(CardElement)
);
console.log(token);
const res = await fetchPostJSON("/api/pay_performer", {
accountID: accountID,
amount: value,
cardToken: token,
});
That API call goes to "/api/pay_performer":
//Handle onboarding a new connect user for x
require("dotenv").config();
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: "2020-08-27",
});
import setCookie from "../../../utils/cookies";
export default async function createPayment(req, res) {
if (req.method === "POST") {
// Connected account ID and the token generated on the client to be saved.
const { accountID, amount, cardToken } = req.body;
console.log(`API side cardToken: ${cardToken.used}`);
try {
// Trying to create a customer with that token.
const customer = await stripe.customers.create({
email: "test2#example.com",
source: cardToken.card,
});
customer.sources = cardToken.card;
// Beginning the cloning process for the connected account.
const token = await stripe.tokens.create(
{
customer: customer.id,
},
{
stripeAccount: accountID,
}
);
const clonedCustomer = await stripe.customers.create(
{
source: token.id,
},
{
stripeAccount: accountID,
}
);
console.log(`Default Source: ${clonedCustomer.default_source}`);
console.log(`AccountID: ${accountID}, Amount: ${amount}`);
const paymentIntent = await stripe.paymentIntents.create(
{
payment_method_types: ["card"],
payment_method: clonedCustomer.default_source
? clonedCustomer.default_source
: null,
amount: amount * 100,
currency: "usd",
application_fee_amount: 1,
customer: clonedCustomer.id,
},
{
stripeAccount: accountID,
}
);
const secret = paymentIntent.client_secret;
console.log(secret);
const payment_method = paymentIntent.payment_method;
return res.status(200).send({ secret, payment_method });
} catch (e) {
console.log(e);
return res.status(500).send({ error: e.message });
}
}
}
But I get an error that the token provided is not a valid token. I am assuming this is because I created the token using the credentials for the connected account on the client and then tried to apply it to the platform account.
How do I have a separate UI to create platform customers FIRST, and then come back and clone them to the connected accounts upon purchase.
Should I not pass a token to the server and just pass the card information over HTTPS? Very lost and the documentation has not helped!
Any help is appreciated!
In order to create a Customer, collect card information and attach a PaymentMethod to them, you could for example use a SetupIntent and Elements [1] or create a PaymentMethod [2]. The core point being, if the system should initially store that information on the platform, the connected account isn't coming into play at all yet. Broadly speaking the steps are (not providing the account id of the connected account anywhere as this is only happening on the platform account), using the platform's secret key:
Create a customer
Collect card data using Stripe.js and Elements
Attach the pm_xxx to the customer in the backend
[1] https://stripe.com/docs/payments/save-and-reuse
[2] https://stripe.com/docs/js/payment_methods/create_payment_method

stripe NodeJS not returning a response

I have a stripe account, I'm trying to set up stripe connect with test custom accounts using node.js. no matter what I do, every time I try to access stripe in any way in the code, it doesn't send a response. No errors, no accounts, just no response at all. I've tried using the secret key, I've tried using the public key. I don't see any requests in the stripe dashboard either. Thanks in advance!
const stripe = require('stripe')('API_KEY')
const registerStripe = async () => {
console.log('getting account')
const account = await stripe.accounts.create({
country: 'US',
type: 'custom',
requested_capabilities: ['card_payments', 'transfers'],
}, function(err, account) {
console.log(err);
console.log(account);
});
console.log(account)
}

Can't Transfer Amount to Connect Stripe Account

BackGround:
What i'm trying to do is set-up a marketplace where the customer can acquire services of a seller,The Project is a MERN Stack Travel Application to be exact. What i would like is for the customer to Pay the Platform(My Website connected with Stripe) when he wishes to acquire a service e.g a hotel Room. The Customer stays at the hotel for the allotted time and when he checksout the platform keeps some of the customers amount as application fee and transfers the rest to the service provider,in this case the hotel.
Current Effort:
I Used STRIPE CONNECT to acheive the required functionality.
(Note: you guys don't need to see all of the code below just the heading and description would give you an idea of what i have done and what i'm trying to ask,but please do read the issue section)
i create a Connect account for the seller when he signs up on my Website
Create 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 });
}
);
});
When the Seller Provides the rest of the required details(including bank Account) after logging-in to the Seller Portal i create a bank_account,update the already created Connect Account and link the bank_account with the Connect Account (Hopefully, that somehow makes sense)
Create Bank 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);
}
);
});
Update Account:
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);
}
);
});
Then when the customers provides his account details i charge the customer,keep some money as application fee and move the rest to the Service Providers Connect account.
Charge Customer
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,
});
}
});
By doing the above procedure a connect account is created and the amount is moved into the connected account.
Issue
The Above procedure although works correctly, it moves the amount into the Connected Service Provider Account directly after the customer is charged, what i would like is for the customer to pay the platform and after the Service Provider has provided his services , the Platform pays the Service Provider, i thought about removing
application_fee_amount,
transfer_data: {
destination: AccountID,
}
the above parameters in the Charge or Stripe.paymentIntents.create endpoint, and after Service Provider has completed his services i transfer the amount using the 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);
}
});
the issue here is that transfer endpoint returns "Your destination account needs to have at least one of the following capabilities enabled: transfers, legacy_payments" , i have checked the Connected Account in Stripe Dashboard and in the Capabilities section Card_Payment and Transfers are both set to Active, plus Payments and Payouts are both Enabled and the status of the connect account is "Complete"
So if anyone could point in the right direction i would really Appreciate it,Cheers :)
Ok - we'll agree that Stripe works as intended. You get the error message that you get because you remove the destination account ID from the payment intent creating function. That's where the problem lies, under your heading Charge Customer.
Let's look at it: (a shortened version)
const payment = await stripe.paymentIntents.create({
amount: PaymentAmount,
currency: "USD",
...
customer: CustomerID,
application_fee_amount,
transfer_data: {
destination: AccountID,
},
confirm: true,
});
The last property confirm: true is equivalent to creating and confirming the payment intent in the same call. The default value is false -- using that the status of the newly created payment intent will be requires_confirmation. And when you're ready, you confirm the payment intent along these lines:
const confirmedPayment = await stripe.paymentIntents.confirm(
'payment_intent_id',
{payment_method: 'card'},
function(err, paymentIntent) {
}
});
A few general comments on things going wrong
When a payer pays money for some goods online, it is the responsibility of the app developer to implement the logic, according to which the money and goods are sent and received: it can be prepaid, postpaid, or partially both. No logic is foolproof. In general, if we worry about customers taking advantage of our payment policy, we can require everything to be prepaid by all paying parties and include a fair refund policy. In this case, Stripe supports refunds of payment intents but what's more important: it keeps track of the status of the payment.
When the payment intent is created but not confirmed, the status is requires_confirmation. Not much can go wrong there. But after the payment intent has been confirmed, the status will be processing - this may take days. You may decide to cancel the payment at any time. But if things go fine, the status will change to succeeded which means that the funds are in the destination account. But if the payment fails for whatever reason, the status will return to requires_payment_method. Even in this case, there's no need to create a new payment or transfer object. You can retrieve the payment intent any time by calling stripe.retrievePaymentIntent(clientSecret) and check the status. But in my opinion, it's much easier to monitor the status changes with a webhook that is configured to receive the status changing events. Even if no action takes place immediately when the status changes, we can store the status in the DB where it's available.
From experience, I've seen how common it is for payments to fail. It doesn't mean that there's any fraud going on on either side but it does mean that the app should be prepared to handle both cases. The events to add to the webhook config are payment_intent.succeeded and payment_intent.payment_failed. How these events are handled is specific to each and every application.
Create a webhook (Stripe config) which includes:
Events sent to the webhook: in this case customer.created, customer.source.created, customer.source.updated
URL = the route that handles the events when they arrive
So you need to store the pending payment in your DB first. Then in the webhook, find it in the DB and complete the transfer.

Resources