I have a question about making sure a site always charges the correct card (or bank account) for a particular customer on a particular site, when connecting multiple websites to a single Stripe account.
- Site A - example1.com is connected to Stripe Account 1 via the API
- Site B - example2.com is connected to Stripe Account 1 via the API
Customer 1 buys something on Site A with Credit Card 1 (say their business card) using their personal email account. Their email is saved with their customer id and their source is set to the business card. Then Customer 1 goes to Site B and buys something on Site B with Credit Card 2 (their personal card), but uses the same email address.
How can I tell Site B to use a particular card each time without updating the default card used for Site A?
Basically before creating an account, I check to make sure that the customer does not already have an account with that particular email. So if the same email is used on Site B as Site A, the same customer account is found for both sites. (Code simplified for explanation purposes.)
<?php
// namespace statement
// use \Stripe statements
function exists ($email) {
$customers = Customer::all(['email' => $email]);
$count = 0;
if (isset ($customers['data'])) {
$count = count($customers['data']);
}
if ($count == 1) {
$c = current ($customers['data']);
if ($c instanceof Customer) {
return $c->id;
}
}
return false;
}
function create_customer ($email) {
// Try to get the customer id by email.
$cid = exists ($email);
// Customer not found create them.
if (!$cid) {
try {
$c = Customer::create([
'email' => $e->mail,
]);
return $c->id;
} catch(Exception $e) {
return false;
}
}
return $cid;
}
function set_source ($cid, $token) {
try {
// This updates the default card, if I am understanding the documentation correctly.
Customer::update($customer_id, ['source' => $token]);
return true;
} catch(Exception $e) {
_sec_set_error_messages ($e, $sandbox_message);
}
return false;
}
function charge ($cid, $amount) {
try {
// This charges the default card, if I am understanding the documentation correctly.
$charge = Charge::create([
'amount' => $amount,
'currency' => 'usd',
'customer' => $cid
]);
return $charge;
} catch (Exception $e) {
return false;
}
}
// check_my_db_for_customer_id();
// Create customer if not found.
$email = 'personal@example.com';
$cid = create_customer ($email);
if ($cid) {
// save_to_my_db($email, $cid);
set_source ($cid, $token);
charge ($cid, $amount);
}
?>
The problem is, when charging the customer, how do tell each site to use a particular card? Can I save a source during my set_source function and then use that same source for a particular site when charging the card?
<?php
function set_source ($cid, $token) {
try {
// Need to add a source if one is found, not update the default.
$get_source_id_somehow = Customer::update($customer_id, ['source' => $token]);
return $get_source_id_somehow;
} catch(Exception $e) {
_sec_set_error_messages ($e, $sandbox_message);
}
return false;
}
function charge ($cid, $amount, $src) {
try {
$charge = Charge::create([
'amount' => $amount,
'currency' => 'usd',
'customer' => $cid,
'source' => $src
]);
return $charge;
} catch (Exception $e) {
return false;
}
}
// check_my_db_for_customer_id_and_source_id();
// Create customer if not found.
$email = 'personal@example.com';
$cid = create_customer ($email);
if ($cid) {
// save_to_my_db($email, $cid);
$src = set_source ($cid, $token);
// save_source_id_to_my_db ($src); ????
charge ($cid, $amount, $src);
}
?>
Or is the only way to accomplish this to connect each site to its own separate Stripe account? Thanks.
EDIT - Additional Question
Would this be a security risk as well? If User 1 signs up on Site A, and then User 2 signs up on Site B using User 1's email, they definitely need to enter new payment details, so that User 1's card is not charged, when User 2 signs up.
EDIT 2 - Additional Thoughts
It seems that customer accounts need to be tied to a single site or application in some way. I don't know if it would validate, but maybe tagging the email or customer with that domain (and their user id) on that site, that way when you search for a matching customer you only find the customer with a matching email for that domain (and making sure it is not in use already by looking for the user id). Otherwise, User 2 could sign-up with User 1's email, and depending on the site may be allowed to change their email without validating the other one first, and in which case you could not update the Stripe customer account for User 2 on Site B without messing it up for User 1 on Site A.
This might lead to the same customer email in your Stripe account multiple times, but you could sort by the tag to find the individual domains at least and see which ones were in use by user id.
Now if User 1 signs up with personal@email.com on Site A it creates a Stripe account, tagged with Site A's domain and their user id. If we don't associate it with an account id, then User 1 might be able to switch their site's email to somethingnew@email.com (if Stripe isn't updated), then if User 2 signs up with personal@email.com on Site A and the account would now find the Stripe account tied to personal@email.com and both accounts would be running from the same Stripe account.
It almost seems like trying to match a customer before creating a new one is a bad security flaw. Or the Stripe account's email needs to stay updated with the site, but additional tags still need to be added to prevent the hijacking of User 1's Stripe account.
Possible Solution - Can anyone confirm?
Create a new Stripe account every time someone registers and tie it to the site's domain user id for future searches is the only secure way I can think to do it.
<?php
$customer = Customer::create([
'email' => 'paying.user.3@example.com',
'metadata' => [
'site' => 'test/',
'uid' => '6'
]
]);
Then you can search like this... Not the most elegant way as you lose the native way to paginate the search results in exchange for a finer result as metadata is not available via Customer::all() [that I know of].
<?php
$customers = Customer::all(['email' => $email]);
$matches = [];
foreach ($customers['data'] as $c) {
if (
$c->metadata && is_object ($c->metadata) &&
$c->metadata->site && $c->metadata->site == 'test/' &&
$c->metadata->uid && $c->metadata->uid == $uid
) {
$matches[] = $c;
}
}