I an using the Stripe Android API to process payments in a mobile application. When a user registers for the app, a Stripe token is generated based on their credit card information. As per the requirement of the project, the app needs to reject prepaid credit cards from being used. Unfortunately, the Android API does not give access to the funding parameter of the Card object returned from the stripe token. This parameter states weather the card is prepaid, debit or credit.
Does anyone know how to access this information on Android? If this is not possible using the Stripe Android API, is there anyway I can directly access the JSON object returned from the Stripe API?
The Stripe Android bindings do not directly expose the card object from the API (which has the funding attribute you're interested in), but you can retrieve it by the token ID using the Java bindings. Something like this should work:
public void onSuccess(Token token) {
com.stripe.model.Token stripeToken = com.stripe.model.Token.retrieve(token.getId(), publishableKey);
com.stripe.model.Card stripeCard = stripeToken.getCard();
if (stripeCard.getFunding().equals("prepaid") {
// Reject card
}
}
Note that this is basically what the Android bindings do when creating the token in the first place (see here).
EDIT: After investigating a bit further, I'm not so sure the above will work. While there is a requestToken() method in the Android bindings that calls the retrieve token API with the publishable key (see here), I don't think it actually works. Calling this endpoint with a publishable key results in an error indicating that the secret key should be used. (I guess it was an undocumented behavior that was removed at some point.)
If this is the case, then I guess you have two options:
patch the Android bindings to make the funding property from the API object available in the Android object (at creation time), or
make the check server-side.
What about this? I used Ywain answer and put it into Async like Stripe SDK do. Works for me.
private class CreateStripeTokenTask extends AsyncTask<Void, Void, com.stripe.model.Token> {
private Map<String, Object> mMap;
protected CreateStripeTokenTask(Card card) {
mMap = hashMapFromCard(card);
}
#Override
protected com.stripe.model.Token doInBackground(Void... params) {
try
{
RequestOptions requestOptions = RequestOptions.builder().setApiKey(<YOUR_STRIPE_PUBLISHABLE_KEY>).build();
com.stripe.model.Token stripeToken = com.stripe.model.Token.create(mMap, requestOptions);
return stripeToken;
}
catch (Exception e)
{
return null;
}
}
protected void onPostExecute(com.stripe.model.Token stripeToken) {
if (stripeToken != null && stripeToken.getCard() != null)
{
if ("prepaid".equals(stripeToken.getCard().getFunding()))
{
//error - prepaid cards are not eligible
}
else
{
//card is fine
}
}
}
private Map<String, Object> hashMapFromCard(Card card) {
Map<String, Object> tokenParams = new HashMap<>();
Map<String, Object> cardParams = new HashMap<>();
cardParams.put("number", TextUtils.nullIfBlank(card.getNumber()));
cardParams.put("cvc", TextUtils.nullIfBlank(card.getCVC()));
cardParams.put("exp_month", card.getExpMonth());
cardParams.put("exp_year", card.getExpYear());
cardParams.put("name", TextUtils.nullIfBlank(card.getName()));
cardParams.put("currency", TextUtils.nullIfBlank(card.getCurrency()));
cardParams.put("address_line1", TextUtils.nullIfBlank(card.getAddressLine1()));
cardParams.put("address_line2", TextUtils.nullIfBlank(card.getAddressLine2()));
cardParams.put("address_city", TextUtils.nullIfBlank(card.getAddressCity()));
cardParams.put("address_zip", TextUtils.nullIfBlank(card.getAddressZip()));
cardParams.put("address_state", TextUtils.nullIfBlank(card.getAddressState()));
cardParams.put("address_country", TextUtils.nullIfBlank(card.getAddressCountry()));
// Remove all null values; they cause validation errors
for (String key : new HashSet<>(cardParams.keySet())) {
if (cardParams.get(key) == null) {
cardParams.remove(key);
}
}
tokenParams.put("card", cardParams);
return tokenParams;
}
}
Related
I want create a payments with 3ds. So i read the docs for one payment and created a code like this:
// init code
final PaymentAuthConfig.Stripe3ds2UiCustomization uiCustomization =
new PaymentAuthConfig.Stripe3ds2UiCustomization.Builder()
.setLabelCustomization(
new PaymentAuthConfig.Stripe3ds2LabelCustomization.Builder()
.setTextFontSize(12)
.build())
.build();
PaymentAuthConfig.init(new PaymentAuthConfig.Builder()
.set3ds2Config(new PaymentAuthConfig.Stripe3ds2Config.Builder()
.setTimeout(5)
.setUiCustomization(uiCustomization)
.build())
.build());
code for a payment
// payment method
PaymentMethodCreateParams params = cardInputWidget.getPaymentMethodCreateParams();
if (params != null) {
Map<String, String> extraParams = new HashMap<>();
extraParams.put("setup_future_usage", "off_session");
ConfirmPaymentIntentParams confirmParams = ConfirmPaymentIntentParams
.createWithPaymentMethodCreateParams(params, paymentIntentClientSecret, null, false, extraParams);
final Context context = getApplicationContext();
stripe = new Stripe(
context,
PaymentConfiguration.getInstance(context).getPublishableKey()
);
stripe.confirmPayment(PaymentUPD.this, confirmParams);
}
code for one payment is work, and its work with 3ds. But now i want to save all cards and then choose some of card from saved list of card.
So i save a card like this:
SourceParams cardSourceParams = SourceParams.createCardParams(cardToSave);
Map<String, Object> params = new HashMap<String, Object>();
params.put("statement_descriptor", nameOfCard);
cardSourceParams.setExtraParams(params);
stripe.createSource(cardSourceParams, new ApiResultCallback<Source>() {
#Override
public void onSuccess(#NonNull Source source) {
Log.e("success", source.getId());
String source_id = source.getId();
// then i save this source to server
}
#Override
public void onError(#NonNull Exception error) {
Log.e("PaymentCore", error.getMessage(), error);
}
});
After my saving card, i get all list of card from server and i have something like this:
brand == Visa
last 4 numbers == 4242
source_id == src_1GSficBnnQZzyRulVXsNTThC1
so now i want to pay with a saved card. So i get
source_id == src_1GSficBnnQZzyRulVXsNTThC1
and try do something like this:
String source = "src_1GSficBnnQZzyRulVXsNTThC1";
SourceParams sourceParams = SourceParams.createSourceFromTokenParams(source);
ConfirmPaymentIntentParams confirmParams = ConfirmPaymentIntentParams
.createWithSourceParams(sourceParams, paymentIntentClientSecret, null);
final Context context = getApplicationContext();
stripe = new Stripe(
context,
PaymentConfiguration.getInstance(context).getPublishableKey()
);
stripe.confirmPayment(PaymentUPD.this, confirmParams);
error:
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter returnUrl
what should i put to returnUrl ? I haw no any returnUrl
My second question is: How i can make a payment with 3ds with a already saved cards?
You should avoid using "sources" for any new Card-related integrations, and should prefer to use Payment Methods instead, just as with your one-time payment.
Stripe has a complete guide for saving card details for later use without payment. Use this if you do not intend to take payment up front. It includes a section for how to take payment with the card later.
If you do intend to take payment right away and save the card for later, follow the guide for that scenario instead.
Note that in all cases if a card has been previously authenticated via 3ds, you must always be ready to handle exceptions or authentication challenges with a recovery flow for your customer.
I am writing an Android App to access Google Drive Photos via CloudRail service. I am able to authenticate to the Google account in question and see all my files/folders in the Google Drive, but I can't access photos from Google Photos.
While browsing through the Drive API documentation, it makes a reference to spaces, specifically 3 spaces are defined: drive, photos and allDataFolder.
Where do I specify the spaces that I am interested in? But default, the drive space is being accessed. Even though, I specifically specified scope for photos:
https://www.googleapis.com/auth/drive.photos.readonly
And when Google Authentication pages opens in the mobile browser, it states that my app wants to gain access to the user's Google Photos and I grant this access. But when calling CloudRail service to get children, no photos are visible
`googledriveChildren = mGoogledriveService.getChildren("/"); // returns goole drive top level files/folders
`googledriveChildren = mGoogledriveService.getChildren("/photos"); // generates a NotFoundException
I have already been down this path and achieved the integration - with the help/guidance from the folks at Cloudrail. You should note that my integration is limited to reading/downloading from Google Photos. I have not found any way to write/upload. Nor have I found any way of reading the album structure that can be set up in Google Photos.
First, you need to include the scope for Google Photos. I did this as follows:
public static final String GOOGLE_PHOTOS_SCOPE = "https://www.googleapis.com/auth/drive.photos.readonly";
private final AtomicReference<CloudStorage> googlephotos = new AtomicReference<>();
List<String> scope = new ArrayList<>();
scope.add(My_Constants.GOOGLE_PHOTOS_SCOPE);
googlephotos.set(new GoogleDrive(context, google_client_id, "", Get.GetString(R.string.google_redirect_uri),
Get.GetString(R.string.google_authentication_state), scope));
((GoogleDrive) googlephotos.get()).useAdvancedAuthentication();
You then need to build a Cloudrail advancedRequest to download whatever data you want. I download the metadata I require as follows:
CloudStrorage service = googlephotos.get();
private void searchForGooglePhotos(final CloudStorage service) throws Throwable {
GoogleDrive google_drive = (GoogleDrive) service;
boolean more = true;
String pageToken = null;
while (more) {
StringBuilder builder = new StringBuilder();
String query = URLEncoder.encode("mimeType='image/jpeg' and trashed = false", "utf-8");
builder.append("/files?spaces=photos");
if (pageToken != null) {
builder.append("&pageToken=");
builder.append(pageToken);
}
builder.append("&q=");
builder.append(query);
builder.append("&fields=nextPageToken,files(id,name,modifiedTime,description,size," +
"imageMediaMetadata(height,rotation,width,time))");
AdvancedRequestSpecification specification = new AdvancedRequestSpecification(builder.toString());
AdvancedRequestResponse response = google_drive.advancedRequest(specification);
#SuppressWarnings("unchecked")
Map<String, Object> resultObjectMap = (Map<String, Object>) response.getBodyJsonParsed();
pageToken = (String) resultObjectMap.get("nextPageToken");
#SuppressWarnings("unchecked")
ArrayList<Map<String, Object>> filesObjectMap = ((ArrayList<Map<String, Object>>) resultObjectMap.get("files"));
for (Map<String, Object> fileObjectMap : filesObjectMap) {
// process downloaded files
}
more = (pageToken != null);
}
}
Subsequently in my app I use Glide to download the photos themselves when required. In the Glide DataFetcher I obtain the inputStream using:
if (model.getSourceRecord().isTypeGooglePhotos()) {
AdvancedRequestSpecification specification;
AdvancedRequestResponse response;
if (model.getIsThumbnail()) {
specification = new AdvancedRequestSpecification("/files" + model.getSourceId() +
"?spaces=photos&fields=thumbnailLink");
response = ((GoogleDrive) service).advancedRequest(specification);
#SuppressWarnings("unchecked")
Map<String, Object> parsed = (Map<String, Object>) response.getBodyJsonParsed();
String link = (String) parsed.get("thumbnailLink");
specification = new AdvancedRequestSpecification(link);
specification.disableBaseUrl();
} else {
specification = new AdvancedRequestSpecification("/files" + model.getSourceId() + "?spaces=photos&alt=media");
}
response = ((GoogleDrive) service).advancedRequest(specification);
input_stream = response.getBodyAsStream();
} else {
if (model.getIsThumbnail()) {
input_stream = service.getThumbnail(model.getSourceId());
} else {
input_stream = service.download(model.getSourceId());
}
}
Here, "model" contains various info associated with each photo. The sourceId comes from the "id" downloaded:
String source_id = java.io.File.separator + fileObjectMap.get("id");
I hope this helps.
Would anyone arriving at this question / response please note that, as of mid Jan 2018, Google have "sunset" (sic) the photos space (spaces=photos above). This means that the above solution no longer works.
On the Google REST API documentation: "The photos space will sunset in early January 2018. Your users can continue to access Google Photos via the drive space by enabling the Google Photos folder in My Drive in the Drive client settings"
Ugh!
I use the odata4j library to access a WCF Data Service.
This is how I call a Service Method from my Android code:
OQueryRequest<OEntity> l = consumer.getEntities("GetDataList")
.custom("dataId", String.format("'%s'", actualData.ID))
.orderBy("Name").skip(0).top(200);
I checked it with WireShark, and I see that every method call is preceded with 2 calls of metadata information request:
Why? Are they essential? The metadata information is quite heavy, it shouldn't request is every time (not to mention 2 times).
What should I do to prevent odata4j from requesting metadata information so many times?
I found in the source code where the 'extra' request happens (in odata4j/odata4j-core/src/main/java/org/odata4j/consumer/AbstractODataConsumer.java ):
#Override
public EdmEntitySet findEdmEntitySet(String entitySetName) {
EdmEntitySet rt = super.findEdmEntitySet(entitySetName);
if (rt == null && delegate != EdmDataServices.EMPTY) {
refreshDelegate();
rt = super.findEdmEntitySet(entitySetName);
}
return rt;
}
It seems that if the entity set can't be found, the consumer creates an extra roundtrip to the server to get the metadata again (by calling refreshDelegate()):
private void refreshDelegate() {
ODataClientRequest request = ODataClientRequest.get(AbstractODataConsumer.this.getServiceRootUri() + "$metadata");
try {
delegate = AbstractODataConsumer.this.getClient().getMetadata(request);
} catch (ODataProducerException e) {
// to support services that do not expose metadata information
delegate = EdmDataServices.EMPTY;
}
}
I don't quite understand why: maybe it assumes that the server has changed and a new version of the metadata is available so it tries again.
If it fails then it tries to find a function with the given name.
Personally I don't consider this very effective unless the server side is so volatile that it changes between calls.
So, if you have no changing metadata on the server, it is safe to remove the check for the entitySet and let it return as a null:
#Override
public EdmEntitySet findEdmEntitySet(String entitySetName) {
EdmEntitySet rt = super.findEdmEntitySet(entitySetName);
//if (rt == null && delegate != EdmDataServices.EMPTY) {
// refreshDelegate();
// rt = super.findEdmEntitySet(entitySetName);
//}
return rt; //if it is null, then the search for a function will commence
}
Is there a way inside of Android Java to create an Entity Key?
For example, inside the Cloud Endpoints Java module code you can do this:
Key<User> userKey= Key.create(User.class, userId);
or with an Ancestor:
Key<Post> postKey= Key.create(userKey, Post.class, postId);
How can you do the above in the Android generated client library? I want to see if I can create a key in Android and pass it to an API method (probably as a websafeKey userKey.getString()).
BONUS: How can you do this with the objective-C Cloud Endpoints client library?
I doubt you want either the datastore nor objectify code in your Android App. That simply not where that belongs. So the way to go is to look at the source of the KeyFactory. In the method keyToString() we can see that most of the magic happens in the class KeyTranslator in method convertToPb().
Here's the code of convertToPb:
public static Reference convertToPb(Key key) {
Reference reference = new Reference();
reference.setApp(key.getAppId());
String nameSpace = key.getNamespace();
if (!nameSpace.isEmpty()) {
reference.setNameSpace(nameSpace);
}
Path path = reference.getMutablePath();
while (key != null) {
Element pathElement = new Element();
pathElement.setType(key.getKind());
if (key.getName() != null) {
pathElement.setName(key.getName());
} else if (key.getId() != Key.NOT_ASSIGNED) {
pathElement.setId(key.getId());
}
path.addElement(pathElement);
key = key.getParent();
}
Collections.reverse(path.mutableElements());
return reference;
}
And here's the code of keyToString()
public static String keyToString(Key key) {
if (!key.isComplete()) {
throw new IllegalArgumentException("Key is incomplete.");
} else {
Reference reference = KeyTranslator.convertToPb(key);
return base64Url().omitPadding().encode(reference.toByteArray());
}
}
Now what you want to do, is to replace the Key stuff in convertToPb with "normal" parameters (type, name/key, parent type, parent name/key) and thus rewrite the method to create a websafeKey without an actual Key object.
It would be much easier though if your app engine API simply accepted the ids and you'd recreate the key on the appengine side of things. My APIs are usually structured like
/user/<userId>/post/<postId>
if i assume an Entity that looks like this
#Entity public class Post {
#Parent Ref<User> user
#Id id; }
Regarding the bonus: What (the heck) is an Objectify Cloud Endpoint? I know Cloud Endpoints and Objectify, but i have not heard of a product that combines the two.
We are using Azure Mobile Services to Push notifications to a Xamarin Android and a Xamarin iOS and a Windows Universal App. The Windows Universal App has plenty of documentation around what we need, although we haven’t had a chance to implement it yet. However, both Xamarin Android and iOS are missing all documentation around Push Notifications. If you go to http://azure.microsoft.com/en-us/documentation/services/mobile-services/ and select Xamarin Android or Xamarin iOS and .NET Backend there are zero links for documentation around these APIs. After digging around a ton yesterday I found this: http://azure.microsoft.com/en-us/documentation/articles/mobile-services-dotnet-backend-xamarin-android-get-started-push/ and http://azure.microsoft.com/en-us/documentation/articles/mobile-services-dotnet-backend-xamarin-ios-get-started-push/ both which were last updated in September of last year. The documentation was promised to be updated over 5 months ago.
When I use the Xamarin Component from Microsoft for Azure Mobile Services: http://components.xamarin.com/view/azure-mobile-services/ I am able to get the MobileServiceClient up and running, but not the Push notifications.
The API:
Push pushManager = MobileService.GetPush();
string deviceId = "what is this???";
//Option 1:
pushManager.RegisterNativeAsync(deviceId);
//Option 2:
GcmRegistration googleNotificationRegistration = new GcmRegistration(deviceId);
pushManager.RegisterAsync(googleNotificationRegistration);
Documentation I’m using:
Push.RegisterAsync:
https://msdn.microsoft.com/en-us/library/microsoft.windowsazure.mobileservices.push.registerasync.aspx
GcmRegistration: I can’t find any documentation for this class
Registration (Base class for GcmRegistration):
https://msdn.microsoft.com/en-us/library/microsoft.windowsazure.mobileservices.registration.aspx
Note: the parameter for Registration is not named deviceId it is named channelUri
Push.RegisterNativeAsync:
https://msdn.microsoft.com/en-us/library/dn643553.aspx
Note: the parameter of RegisterNativeAsync is not named deviceId it is named channelUri
My question is simple:
What is deviceId supposed to be? And how do I get it?
All the documentation above is for Winodws Universal Apps not for Xamarin Apps on Mono.
In the writing up of this question I have found articles about "Get Started with Notification Hubs":
Xamarin iOS - http://azure.microsoft.com/en-us/documentation/articles/partner-xamarin-notification-hubs-ios-get-started/
Xamarin Android - http://azure.microsoft.com/en-us/documentation/articles/partner-xamarin-notification-hubs-android-get-started/
Are these the example I should be using? They look old and the Android one mentions nothing about Azure Mobile Services. Should I not even be using the Azure Mobile Services Xamarin Component for Android?
tl;dr
deviceId should be just the GCMRegistrationId.
I looked into the source code of the implementations of the component DLLs and also Android SDKs.
Firstly, let's take a look to your option 1 and option 2 behind the scene. Basically both eventually do the same job of creating a GcmRegistration and passing it the internal RegistrationManager.
public Task RegisterAsync (Registration registration)
{
if (registration == null) {
throw new ArgumentNullException ("registration");
}
if (string.IsNullOrWhiteSpace (registration.PushHandle)) {
throw new ArgumentNullException ("registration.deviceId");
}
return this.RegistrationManager.RegisterAsync (registration);
}
public Task RegisterNativeAsync (string deviceId, IEnumerable<string> tags)
{
if (string.IsNullOrWhiteSpace (deviceId)) {
throw new ArgumentNullException ("deviceId");
}
GcmRegistration registration = new GcmRegistration (deviceId, tags);
return this.RegistrationManager.RegisterAsync (registration);
}
Then, one of the API calls that I can find involving the Registration.PushHandle (which is the deviceId you passed) is as below
public async Task<IEnumerable<Registration>> ListRegistrationsAsync (string deviceId)
{
MobileServiceHttpResponse mobileServiceHttpResponse = await this.client.HttpClient.RequestAsync (HttpMethod.Get, string.Format ("/push/registrations?deviceId={0}&platform={1}", new object[] {
Uri.EscapeUriString (deviceId),
Uri.EscapeUriString (Platform.Instance.PushUtility.GetPlatform ())
}), this.client.CurrentUser, null, true, null, MobileServiceFeatures.None);
return JsonConvert.DeserializeObject<IEnumerable<Registration>> (mobileServiceHttpResponse.Content, new JsonConverter[] {
new RegistrationConverter ()
});
}
I have then switched to Android Mobile Services SDK to look for similar code to find some hints. Sadly, it is found called pnsHandle in android but still no hints what it is.
/**
* Registers the client for native notifications with the specified tags
* #param pnsHandle PNS specific identifier
* #param tags Tags to use in the registration
* #return The created registration
* #throws Exception
*/
public Registration register(String pnsHandle, String... tags) throws Exception {
if (isNullOrWhiteSpace(pnsHandle)) {
throw new IllegalArgumentException("pnsHandle");
}
Registration registration = PnsSpecificRegistrationFactory.getInstance().createNativeRegistration(mNotificationHubPath);
registration.setPNSHandle(pnsHandle);
registration.setName(Registration.DEFAULT_REGISTRATION_NAME);
registration.addTags(tags);
return registerInternal(registration);
}
Finally, I guess the below example code from http://azure.microsoft.com/en-us/documentation/articles/mobile-services-dotnet-backend-android-get-started-push/#update-app should be calling the same API which now explain everything, i.e. deviceId is just the GCMRegistrationId.
#Override
public void onRegistered(Context context, final String gcmRegistrationId) {
super.onRegistered(context, gcmRegistrationId);
new AsyncTask<Void, Void, Void>() {
protected Void doInBackground(Void... params) {
try {
ToDoActivity.mClient.getPush().register(gcmRegistrationId, null);
return null;
}
catch(Exception e) {
// handle error
}
return null;
}
}.execute();
}