Stripe, is it possible to search a customer by their email? - api

Update: Since around January 2018, it is now possible to search using the email parameter on Stripe. See the accepted answer.
I was wondering if it was possible to search a customer only by their email address when using the Stripe API.
The documentation only indicates searching by:
created,
ending_before,
limit,
starting_after
But not email.
I'd like to avoid having to list over all my customers to find which ones have the same email addresses.

Stripe now allows you to filter customers by email.
https://stripe.com/docs/api#list_customers
Map<String, Object> options = new HashMap<>();
options.put("email", email);
List<Customer> customers = Customer.list(options).getData();
if (customers.size() > 0) {
Customer customer = customers.get(0);
...
This is important to help ensure you don't create duplicate customers. Because you can't put creating a customer in Stripe and the storage of the Stripe customer ID in your system inside a single transaction you need to build in some fail safes that check to see if a particular customer exists before you create a new one. Searching customers by email is important in that regard.

I did this by using the following API request. This was not available in stripe docs.I got this by tracking down their search in the dashboard area using Browser Developer Tools.
url :https://api.stripe.com/v1/search?query="+email+"&prefix=false",
method: GET
headers: {
"authorization": "Bearer Your_seceret Key",
"content-type": "application/x-www-form-urlencoded",
}
Warning This uses an undocumented API that is specific to the dashboard. While it might work today, there is no guarantee it will continue to work in the future.

You need to retrieve and store the Stripe customer ID along with the other customer details in your database. You can then search for the email address in your database and retrieve the customer record from Stripe by using the Stripe customer ID.

UPDATE: Stripe now allows searching via email
https://stripe.com/docs/api/php#list_customers
/**
* Remember that Stripe unfortunately allows multiple customers to have the same email address.
* #see https://stackoverflow.com/a/38492724/470749
*
* #param string $emailAddress
* #return array
*/
public function getCustomersByEmailAddress($emailAddress) {
try {
$matchingCustomers = [];
$lastResult = null;
$hasMoreResults = true;
while ($hasMoreResults) {
$searchResults = \Stripe\Customer::all([
"email" => $emailAddress,
"limit" => 100,
"starting_after" => $lastResult
]);
$hasMoreResults = $searchResults->has_more;
foreach ($searchResults->autoPagingIterator() as $customer) {
$matchingCustomers[] = $customer;
}
$lastResult = end($searchResults->data);
}
return $matchingCustomers;
} catch (\Exception $e) {
Log::error($e);
return [];
}
}

You can't directly search by email.
However, you can hack a little bit to list all users, and look after your email.
Here's my code (PHP) :
$last_customer = NULL;
$email = "EmailYou#AreLooking.for";
while (true) {
$customers = \Stripe\Customer::all(array("limit" => 100, "starting_after" => $last_customer));
foreach ($customers->autoPagingIterator() as $customer) {
if ($customer->email == $email) {
$customerIamLookingFor = $customer;
break 2;
}
}
if (!$customers->has_more) {
break;
}
$last_customer = end($customers->data);
}

You only need to write this line
\Stripe\Customer::all(["email" => "YourDesiredEmail"]);

In NodeJs we can search for our desired customer with their email address like the following:
const stripeSecretKey = process.env.STRIPE_SECRET_KEY;
const stripe = require('stripe')(stripeSecretKey);
const findCustomerByEmail = async (email) => {
try {
const customer = await stripe.customers.list( {
email: email,
limit: 1
});
if(customer.data.length !== 0){
return customer.data[0].id;
}
} catch (e) {
return (e);
}
};
The actual call to stripe is using the stripe.customers.list. If the email exists in our stripe account then the returned object will contain an element called data.

Using "list()" and "search()", you can get customers by email with these Python code below:
customers = stripe.Customer.list(
email="example#gmail.com",
)
customer = stripe.Customer.search(
query="email:'example#gmail.com'"
)
You can also limit customers to get with "limit" parameter as shown below:
customers = stripe.Customer.list(
email="example#gmail.com",
limit=3
)
customer = stripe.Customer.search(
query="email:'example#gmail.com'",
limit=3
)

Since you specified that
The documentation only indicate to search by created, ending_before, limit and starting_after, but no "email".
You are right, you can't search using emails.
If you still wish to do that, What you can do instead is to get a list of all the customer and filter on the response you get using email.
For Example, in ruby you can do it as follows:
customers = Stripe::Customer.all
customer_i_need = customers.select do |c|
c.email == "foo#bar.com"
end
PS: Stripe can have multiple customers associated with one email address.

Please bear in mind when using Stripe API that it is case sensitive email (which is a bit stupid). Hopefully they change this.

Stripe API does not supports any search-by-email feature. They have this search in their dashboard but not released any to API; from the architectural concept it seems that there is no possibility or plan from stripe to include this in API; every object in their API is retrievable only by that specific objects id given by stripe while its created. May be, they have kept it as a scope for third party application developers involvement!!
So, the obvious solution is to store the customers in your own database that you want to be searchable in future - as Simeon Visser has said above
btw, for a workaround, if you already have used the stripe API a lot and there are many customer data which you now need to be searchable - the only way is to go thru the 'List all customers' functionality of API & build the database for your own purpose; ofcourse, you've to use pagination shortcut to iterate thru the whole list for doing so.
$customers = \Stripe\Customer::all(array("limit" => 3));
foreach ($customers->autoPagingIterator() as $customer) {
// Do something with $customer
}

You can try this. It worked for me. Below code will return empty list if not found data matching with email.
$stripe = new \Stripe\StripeClient("YOUR_STRIPE_SECRET");
$customers = $stripe->customers->all(['email'=>'jane.doe#example.com',
'limit' => 15,
]);

Stripe Search API Beta now is available
youtube link

Here is The Async- Await Way This Method can Be Used For All Third Party Hits with Nodejs Particularly
const configuration = {
headers: {
"authorization": `Bearer ${Your stripe test key}`,
"content-type": "application/x-www-form-urlencoded",
}
};
const requestUrl = `https://api.stripe.com/v1/search?
query=${'email you are to use'} &prefix=false`
const emailPayment = await axios.get(requestUrl,
configuration)
Axiom is Npm for creating http requests... very cool and dead simple

Stripe allows the ability to have more than once customer with the same email. That said, if you wanted to, you can pass a filters hash param to the list method to return a single customer or array of customers.
Stripe::Customer.list(email: user.email).data
The above will return an array of Stripe::Customer instances with the email

Related

Cloudflare sessions

I wanted to add a feature to my site similar to this:
When a new session starts look at the utm_source/utm_medium querystring values and also the referrer. Based on that display a differnt phone number of the site, so for example google cpc, bing cpc, google organic, bing organic would have different numbers.
The number of calls to each number should then give an indication of which traffic source generated the calls.
The problem is, because we're using clouldflare, if a user is served a page from the cache then there is no session_start event on the origin server.
Is there a solution to get around this? Is there anyway to do this on cloudflare itself, perhaps using its "workers"?
Thanks
Cloudflare workers can be used to accomplish that. The worker script will first need to determine which phone number to show. This can be done by checking the query params or a cookie or any other aspect of the request. Then the worker script can take the original response body (from the cache or the origin server) and replace all occurences of the original phone number with the new phone number.
Here's an example worker script that does that. To determine which phone number to show, it will first check the query parameters, as you mentioned. When it sees the utm_source query param, it will also set a cookie that can then be checked in all subsequent requests to show the same phone number.
// The name of the cookie that will be used to determine which phone number to show
const cookieName = "phone_num_id";
// The list of all phone numbers to use
const phoneNumbers = [
{
id: "google-cpc",
utmSource: "google",
utmMedium: "cpc",
phoneNumber: "222-222-2222"
},
{
id: "bing-cpc",
utmSource: "bing",
utmMedium: "cpc",
phoneNumber: "333-333-3333"
}
];
// This adds a "fetch" event listener which will be called for all incoming requests
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
// Forward the incoming request and get the original response. If Cloudflare has already cached
// this request, this will return the cached response, otherwise it will make the request to
// the origin
let response = await fetch(request);
// Check the content type of the response and fallback to an empty string
// if there is no content-type header
let contentType = response.headers.get("content-type") || "";
// We're only interested in changing respones that have a content-type starting
// with "text/html". Anything else will be returned without any modifications
if (/^text\/html/.test(contentType)) {
// `newPhoneNumberData` will be the new phone number to show (if any)
let newPhoneNumberData;
// searchParams are the query parameters for this request
let searchParams = new URL(request.url).searchParams;
// If the request has a `utm_source` query param, use that to determine which phone number to show
if (searchParams.has("utm_source")) {
let utmSource = searchParams.get("utm_source") || "";
let utmMedium = searchParams.get("utm_medium") || "";
// Lookup the phone number based on the `utmSource` and `utmMedium`
newPhoneNumberData = phoneNumbers.find(
phoneNumber =>
phoneNumber.utmSource === utmSource &&
phoneNumber.utmMedium === utmMedium
);
// If we found a match, set a cookie so that subsequent requests get the same phone number
if (newPhoneNumberData) {
// In order to modify the response headers, we first have to duplicate the response
// so that it becomes mutable
response = new Response(response.body, response);
// Now set a cookie with the id of the new phone number to use. You should modify the properties
// of the cookie for your use case. See this page for more information:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
response.headers.append(
"Set-Cookie",
`${cookieName}=${newPhoneNumberData.id}; Max-Age=2147483647`
);
}
}
// If we weren't able to determine the new phone number based on the query params, try
// checking the cookies next
if (!newPhoneNumberData) {
let cookieHeader = request.headers.get("cookie") || "";
// split each of the cookies and remove leading/trailing whitespace
let cookies = cookieHeader.split(";").map(str => str.trim());
// Find the phone number cookie
let phoneNumberCookieString = cookies.find(cookieString =>
cookieString.startsWith(`${cookieName}=`)
);
// If the request has the phone number cookie, use that
if (phoneNumberCookieString) {
// Extract the phone number id from the cookie
const phoneNumberId = phoneNumberCookieString.split("=")[1];
// Lookup the phone number data based on the ID
newPhoneNumberData = phoneNumbers.find(
phoneNumber => phoneNumber.id === phoneNumberId
);
}
}
// If we found a matching phone number to use, now we'll need to replace all occurences
// of the original phone number with the new one before returning the response
if (newPhoneNumberData) {
// Get the original response body
let responseBody = await response.text();
// Use a regex with the `g` flag to find/replace all occurences of the original phone number.
// This would look for a phone number like "(111)-111-1111" but you can modify this to fit
// however your original phone number appears
responseBody = responseBody.replace(
/\(?111\)?[-\s]*111[-\s]*1111/g,
newPhoneNumberData.phoneNumber
);
// Create a new response with the updated responseBody. We also pass the original `response` as the
// second argument in order to copy all other properties from the original response (status, headers, etc)
response = new Response(responseBody, response);
}
}
return response;
}

API Request Pagination

I am making a simple API request to Github to get all the repositories. The problem is that Github has a limitation and the max that it can send is 100 per request. There are users that have more than 100 repositories and I don't know how to access it or how to make pagination.
I am making GET request with Axios like this:
https://api.github.com/users/<AccountName>/repos?per_page=100
I can also put page number like so
https://api.github.com/users/<AccountName>/repos?page=3&per_page=100
But how do I make this work in app without making 10 API requests? I wouldn't even know how many requests I should make because I don't know what is the number that gets returned, does somebody have 100 or 1000 repos? I would like for everything to be returned and saved in array, for example.
EDIT:
Example: I am passing in accountName
var config = {
headers: {'Authorization': `token ${ACCESS_TOKEN}`}
}
const REQUEST: string = 'https://api.github.com/users/'
const apiCall = {
getData: async function (accountName) {
const encodedAccountName = encodeURIComponent(accountName)
const requestUrl = `${REQUEST}${encodedAccountName}`
const user = await axios.get(requestUrl, config)
// This return user and inside of user there is a link for fetching repos
const repo = await axios.get(`${user.data.repos_url}?per_page=100`, config)
...
You can get the repo count by requesting from the user account URL first. For example here is mine:
https://api.github.com/users/erikh2000
The response there includes a "public_repos" value. Bam! That's the magic number you want.
You next need to make multiple fetches if the repo count is over 100. I know you didn't want to, but hey... can't blame web services for trying to conserve their bandwidth. The good news is you can probably put them in a Promise.all() block and have them all fetch together and return at once. So code like...
const fetchAllTheRepos = (userName, repoCount) => {
const MAX_PER_PAGE = 100;
const baseUrl = 'https://api.github.com/users/' + userName +
'/repos?per_page=' + MAX_PER_PAGE;
//Start fetching every page of repos.
const fetchPromises = [], pageCount = Math.ceil(repoCount /
MAX_PER_PAGE);
for (let pageI = 1; pageI <= pageCount; ++pageI) {
const fetchPagePromise = fetch(baseUrl + '&page=' + pageI);
fetchPromises.push(fetchPagePromise);
}
//This promise resolves after all the fetching is done.
return Promise.all(fetchPromises)
.then((responses) => {
//Parse all the responses to JSON.
return Promise.all( responses.map((response) => response.json()) );
}).then((results) => {
//Copy the results into one big array that has all the friggin repos.
let repos = [];
results.forEach((result) => {
repos = repos.concat(result);
});
return repos;
});
};
//I left out the code to get the repo count, but that's pretty easy.
fetchAllTheRepos('erikh2000', 7).then((repos) => {
console.log(repos.length);
});
Simultaneously fetching all the pages may end up being more than Github wants to let you do at once for those accounts with lots of repos. I would put some "good citizen" limit on the number of repos you'll try to get at once, e.g. 1000. And then see if api.github.com agrees with your definition of a good citizen by watching for HTTP error responses. You can get into throttling solutions if needed, but probably a "grab it all at once" approach like above works fine.
On the other hand, if you are spidering through multiple accounts in one session, then maybe design the throttling in from the beginning just to you know... be nice. For that, look at a queue/worker pattern.

Limit Google Sign-In to .edu accounts in Meteor

I'm trying to limit my Google + Sign-In Button to only allow #something.edu accounts to sign in. How would I go about doing this. This is my code so far:
Template.googleLogin.events({
'click #gLogin': function(event) {
Meteor.loginWithGoogle({}, function(err){
if (err) {
throw new Meteor.Error("Google login didn't work!");
}
else {
Router.go('/home')
}
});
}
})
Template.primaryLayout.events({
'click #gLogout': function(event) {
Meteor.logout(function(err){
if (err) {
throw new Meteor.Error("Hmm looks like your logout failed. ");
}
else {
Router.go('/')
}
})
}
})
You can accomplish this using Accounts.config (in the root directory, so it runs on both the client and server)
Accounts.config({ restrictCreationByEmailDomain: 'something.edu' })
If you need something more custom, you can replace something.edu with a method if you need to fine grain your requirement, i.e for any .edu domain:
Accounts.config({ restrictCreationByEmailDomain: function(address) {
return new RegExp('\\.edu$', 'i')).test(address)
}
});
The accounts package allows configuring account creation domain through:
Accounts.config({
restrictCreationByEmailDomain: 'something.edu'
})
But this has some limitations in case of google:
This is only client side and only allows for the login form to get properly styled to represent the domain's logo etc. But it can be very easily overcome by crafting the google oauth signin url by hand
In case you need to configure extra options like allowing multiple domains or a domain and some outside users (perhaps third party contractors or support from a software company etc) this does not work. In case of accounts-google, the package checks if restrictCreationByEmailDomain is a String and if it is instead a function, it just discards it.
Therefore, to be able to properly and securely utilize such functionality, you need to use the official Accounts.validateNewUser callback:
Accounts.validateNewUser(function(newUser) {
var newUserEmail = newUser.services.google.email;
if (!newUserEmail) throw new Meteor.Error(403,'You need a valid email address to sign up.');
if (!checkEmailAgainstAllowed(newUserEmail)) throw new Meteor.Error(403,'You need an accepted organization email address to sign up.');
return true;
});
var checkEmailAgainstAllowed = function(email) {
var allowedDomains = ['something.edu'];
var allowedEmails = ['someone#example.com'];
var domain = email.replace(/.*#/,'').toLowerCase();
return _.contains(allowedEmails, email) || _.contains(allowedDomains, domain);
};
If you want to be extra cautious, you can implement the same for the Accounts.validateLoginAttempt and Accounts.onCreateUser callbacks as well.

getting user info google plus api

How can I get public info of a user from google plus login button integrated on the site, here is the code which is giving me email, I need more info which is provide by google plus :
<div id="signin-button" class="show">
<div class="g-signin" data-callback="loginFinishedCallback"
data-approvalprompt="force"
data-clientid="9076269517.apps.googleusercontent.com"
data-scope="https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/userinfo.email"
data-height="short"
data-cookiepolicy="single_host_origin">
</div>
java script :
function loginFinishedCallback(authResult) {
if (authResult) {
if (authResult['error'] == undefined){
gapi.auth.setToken(authResult); // Store the returned token.
toggleElement('signin-button'); // Hide the sign-in button after successfully signing in the user.
getEmail(); // Trigger request to get the email address.
} else {
console.log('An error occurred');
}
} else {
console.log('Empty authResult'); // Something went wrong
}
}
function getEmail(){
// Load the oauth2 libraries to enable the userinfo methods.
gapi.client.load('oauth2', 'v2', function() {
var request = gapi.client.oauth2.userinfo.get();
request.execute(getEmailCallback);
});
}
function getEmailCallback(obj){
var el = document.getElementById('email');
var email = '';
if (obj['email']) {
email = 'Email: ' + obj['email'];
}
//console.log(obj); // Uncomment to inspect the full object.
el.innerHTML = email;
toggleElement('email');
}
function toggleElement(id) {
var el = document.getElementById(id);
if (el.getAttribute('class') == 'hide') {
el.setAttribute('class', 'show');
} else {
el.setAttribute('class', 'hide');
}
}
I tried replacing email with name, userId but getting nothing from these variables.
How can I get basic information of a user when he is logged in through google plus.
Similar to how you have loaded the oauth2 v2 client package using gapi.client.load, you will use this again to load the plus v1 client package. This will give you a number of packages and methods under the gapi.client.plus namespace.
The Plus API includes a package to load information about People, including getting them by their User ID or, since they have authenticated with you, you can use the special identifier "me".
Full details and an example are given at https://developers.google.com/+/api/latest/people/get, but here is an (untested) similar function to your getEmail() method that would get their full name:
function getFullName(){
// Load the Plus library to get the People package and methods
gapi.client.load('plus', 'v1', function() {
var request = gapi.client.plus.people.get('me');
request.execute(getFullNameCallback);
});
};
function getFullNameCallback(obj){
var el = document.getElementById('email');
var name = '';
if (obj['displayName']) {
name = 'Name: '+obj.displayName;
}
el.innerHTML = name;
toggleElement('name');
};
The above code snippet no longer seems to work.
Once again we are chasing our tails over something
google now changed......
error "Access Not Configured. Please use Google Developers Console to activate the API for your project."
I assumed it it might be the "Google+ API" so it is switched on in the developer console,
still no working however.
Yet api explorer shows promisingly that some sort of code can work,
however its a dogs breakfast trying to discern what javascript code is working there.
So so useful api explorer...., but how about google show a simple WORKING example in code that we can look at for this same request?

Meteor.http.get issue with Twitter API

I am using Meteor and the Twitter API for a project. I want to get information on a user from Twitter. I wrote a function that for example returns only the location of a user from Twitter. I believe this is the proper way to do a request on Meteor. Here it is :
Meteor.methods({getTwitterLocation: function (username) {
Meteor.http.get("https://api.twitter.com/1/users/show.json?screen_name="+ username +"&include_entities=true", function(error, result) {
if (result.statusCode === 200) {
var respJson = JSON.parse(result.content);
console.log(respJson.location);
console.log("location works");
return (respJson.location)
}else {
return ( "Unknown user ")
}
});
}});
Now this function will log what's in the console on my Git Bash. I get someones Location by doing a Meteor.call. But I want to post what that function returns on a page. In my case, I want to post in on a user's profile. This doesn't work. But the console.log(respJson.location) returns the location in my Git Bash but it won't display anything on the profile page. This is what I did on my profile page:
profile.js :
Template.profile.getLocation= function(){
return Meteor.call("getTwitterLocation","BillGates");
}
profile.html :
<template name="profile">
from {{getLocation}}
</template>
With that I get "Seattle, WA" and " "location works" on my Git Bash but nothing on the profile page. If anyone knows what I can do, that'd be really appreciated. Thanks.
Firstly when data is returned from the server you need to use a synchronous call, as the callback will return the data when the server already thinks the meteor method has completed. (the callback will be fired at a later time, when the data is returned from the server, by which time the meteor client would have already got a response)
var result = Meteor.http.get("https://api.twitter.com/1/users/show.json?screen_name="+ username +"&include_entities=true");
if (result.statusCode === 200) {
var respJson = JSON.parse(result.content);
console.log(respJson.location);
console.log("location works");
return (respJson.location)
}else {
return ( "Unknown user ")
}
The second is you need to use a Session hash to return the data from the template. This is because it will take time to get the response and the getLocation would expect an instant result (without a callback). At the moment client side javascript can't use synchronous api calls like on the server.
Template.profile.getLocation= function(){
return Session.get("twitterlocation");
}
Use the template created event to fire the meteor call:
Template.profile.created = function() {
Meteor.call("getTwitterLocation","BillGates", function(err,result) {
if(result && !err) {
Session.set("twitterlocation", result);
}
else
{
Session.set("twitterlocation", "Error");
}
});
});
Update:
Twitter has since updated its API to 1.1 a few modifications are required:
You now need to swap over to the 1.1 api by using 1.1 instead of 1. In addition you need to OAuth your requests. See https://dev.twitter.com/docs/auth/authorizing-request. Below contains sample data but you need to get proper keys
var authkey = "OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog",
oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp=""+(new Date().getTime()/1000).toFixed(0)+"",
oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
oauth_version="1.0"";
Be sure to remove the newlines, I've wrapped it to make it easy to read.
var result = Meteor.http.get("https://api.twitter.com/1.1/users/show.json?screen_name="+ username +"&include_entities=true",{headers:{Authorization : authkey});
If you find this a bit troublesome it might be easier to just use a package like https://github.com/Sewdn/meteor-twitter-api via meteorite to OAuth your requests for you.