Having trouble getting the format right with Parse.Cloud.httpRequest for deleting a subscription at_period_end.
I am able to successfully make this request with PostMan using x-www-form-urlencoded, key 'at_period_end' value true. (Can't post a screenshot due to my reputation sorry)
Here is my cloud-code:
Parse.Cloud.httpRequest({
method : 'DELETE',
url : 'https://' + skey + ':#' + 'api.stripe.com/v1' + '/customers/' + request.params.customerId + '/subscriptions/' + request.params.subscriptionId,
body : {
"at_period_end": true
},
success: function(httpResponse) {
if (httpResponse.status === 200) {
response.success(httpResponse);
}
else {
response.error(httpResponse);
}
},
error: function(httpResponse) {
response.error(httpResponse);
}
});
I have played around with adding a headers object with Content-Type set, but unsuccessful.
I think this is just a formatting translation issue from what I correctly entered into PostMan, to what is in my httpRequest object...
I also can't find any great information on docs on the httpRequest method so its quite frustrating :(.
Thanks heaps
***** EDIT ****** SOLUTION:
Managed to solve this using url inline parameters:
var options = request.params.options,
url = 'https://' + skey + ':#api.stripe.com/v1/customers/' + request.params.customerId + '/subscriptions/' + request.params.subscriptionId,
keys;
keys = Object.keys(options);
// This is disgusting, I need to know a better way.
for (var i = 0; i < keys.length; i++)
{
if (i === 0)
{
url += '?';
}
url += keys[i] + '=' + options[keys[i]];
if (i !== keys.length - 1)
{
url += '&';
}
}
Parse.Cloud.httpRequest({
method : 'DELETE',
url : url,
success: function(httpResponse) {
if (httpResponse.status === 200) {
response.success(httpResponse);
}
else {
response.error(httpResponse);
}
},
error: function(httpResponse) {
response.error(httpResponse);
}
});
if anyone could show me a better way to write this, that would be epic :)
Cheers
This one has always been particularly thorny for me, here is what I've been using that has worked:
Parse.Cloud.httpRequest({
method: 'DELETE',
url: 'https://api.stripe.com/v1/customers/' + request.params.stripeId + '/subscriptions/' + request.params.stripeSubscriptionId,
headers: {
'Authorization': 'Basic BASE_64_ENCODE_SECRET_KEY'
},
params: {
at_period_end: true
},
success: function(httpResponse) {
...
},
error: function(httpResponse) {
...
}
});
A couple of extra details here.
I initially had "Content-Type: application/json" as one of the headers, but this appears to not be correct, despite (I think) needing it in the past.
The base64 encode of your key can be generated with
echo -e 'sk_live_ABCDEF123456:' | openssl base64
Don't forget the colon (:) at the end of the key, it matters.
This is just a detail however, and it looks like putting the secret key directly in the URL is working as well.
Related
I want to create a new document in Firestore using the REST API.
Very good examples here using Axios to send the POST request with some fields:
https://www.jeansnyman.com/posts/google-firestore-rest-api-examples/
axios.post(
"https://firestore.googleapis.com/v1/projects/<PROJECTIDHERE>/databases/(default)/documents/<COLLECTIONNAME>",
{
fields: {
title: { stringValue: this.title },
category: { stringValue: this.category },
post: { stringValue: this.post },
summary: { stringValue: this.description },
published: { booleanValue: this.published },
created: { timestampValue: new Date() },
modified: { timestampValue: new Date() }
}
}
).then(res => { console.log("Post created") })
And an example here using Python Requests:
Using the Firestore REST API to Update a Document Field
(this is a PATCH request but the field formatting is the same as in a POST request)
import requests
import json
endpoint = "https://firestore.googleapis.com/v1/projects/[PROJECT_ID]/databases/(default)/documents/[COLLECTION]/[DOCUMENT_ID]?currentDocument.exists=true&updateMask.fieldPaths=[FIELD_1]"
body = {
"fields" : {
"[FIELD_1]" : {
"stringValue" : "random new value"
}
}
}
data = json.dumps(body)
headers = {"Authorization": "Bearer [AUTH_TOKEN]"}
print(requests.patch(endpoint, data=data, headers=headers).json())
I am using Google Apps Script UrlFetchApp.fetch to send my requests. I am able to use GET requests with no problems. For example, to get all the documents in a collection (in Google Apps Script):
function firestore_get_documents(){
var options = {
headers: { 'Authorization': 'Bearer ' + ScriptApp.getOAuthToken() },
method:'GET'
}
var response = UrlFetchApp.fetch('https://firestore.googleapis.com/v1/projects/<PROJECTIDHERE>/databases/(default)/documents/myCollection', options);
var parsed = JSON.parse(response.getContentText());
return parsed;
}
This works nicely. And changing 'method' to 'POST' creates a new document in myCollection as expected. Then I try to add a POST body with some fields (or just one field):
function firestore_create_new_document(){
var options = {
headers: { 'Authorization': 'Bearer ' + ScriptApp.getOAuthToken() },
method:'POST',
payload: {fields: { title: { stringValue: 'newTitle' } } }, // If you comment out this line, it works as expected
muteHttpExceptions:true
}
var response = UrlFetchApp.fetch('https://firestore.googleapis.com/v1/projects/<PROJECTIDHERE>/databases/(default)/documents/myCollection', options);
var contentText = response.getContentText();
var parsed = JSON.parse(response.getContentText());
return parsed;
}
I get the following errors:
code: 400 message: "Request contains an invalid argument."
status: "INVALID_ARGUMENT"
details[0][#type]: "type.googleapis.com/google.rpc.BadRequest"
details[0][fieldViolations][0][field]: "{title={stringValue=newTitle}}"
details[0][fieldViolations][0][description]: "Error expanding 'fields' parameter. Cannot find matching fields for path '{title={stringValue=newTitle}}'."
Documentation is available here:
https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents/createDocument
https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents#Document
The problem may be the formatting of my 'fields' object - I've tried several different formats from the documentation and examples
The problem may be that the fields don't exist yet? I think I should be able to create a new document with new fields
The problem may be with the way UrlFetchApp.fetch sends my JSON body. I have tried using payload = JSON.stringify(payload_object) and that doesn't work either.
I think UrlFetchApp is doing something slightly different than Axios or Python Requests - the body is getting sent differently, and not parsing as expected.
How about the following modification?
From:
var options = {
headers: { 'Authorization': 'Bearer ' + ScriptApp.getOAuthToken() },
method:'POST',
payload: {fields: { title: { stringValue: 'newTitle' } } }, // If you comment out this line, it works as expected
muteHttpExceptions:true
}
To:
var options = {
headers: { 'Authorization': 'Bearer ' + ScriptApp.getOAuthToken() },
method:'POST',
payload: JSON.stringify({fields: { title: { stringValue: 'newTitle' } } }),
contentType: "application/json",
muteHttpExceptions:true
}
When I tested above modified request, I could confirm that it worked. But if other error occurs, please tell me.
Reference:
Class UrlFetchApp
I am moving from Leaflet to Mapbox GL and have some data issues. My webApi is proven but I cannot smoothly integrate them.
The approach I gave up on, based upon their examples and my own research, looks like:
map = new mapboxgl.Map({
container: 'mapdiv',
style: 'mapbox://styles/mapbox/streets-v10'
, center: start
, zoom: $scope.zoom
, transformRequest: (url, resourceType) => {
if (resourceType === 'Source' && url.startsWith(CONFIG.API_URL)) {
return {
headers: {
'Authorization': 'Bearer ' + localStorageService.get("authorizationData")
, 'Access-Control-Allow-Origin': CONFIG.APP_URL
, 'Access-Control-Allow-Credentials': 'true'
}
}
}
}
});
This is passing my OAuth2 token (or at least I think it should be) and the Cross site scripting part CORS.
Accompanying the above with:
map.addSource(layerName, { type: 'geojson', url: getLayerURL($scope.remLayers[i]) });
map.getSource(layerName).setData(getLayerURL($scope.remLayers[i]));
Having also tried to no avail:
map.addSource(layerName, { "type": 'geojson', "data": { "type": "FeatureCollection", "features": [] }});
map.getSource(layerName).setData(getLayerURL($scope.remLayers[i]));
Although there are no errors Fiddler does not show any requests being made to my layer webApi. All the others show but Mapbox does not appear to raising them.
The Url looks like:
http://localhost:49198/api/layer/?bbox=36.686654090881355,34.72821077223763,36.74072742462159,34.73664000652042&dtype=l&id=cf0e1df7-9510-4d03-9319-d4a1a7d6646d&sessionId=9a7d7daf-76fc-4dd8-af4f-b55d341e60e4
Because this was not working I attempted to make it more manual using my existing $http calls which partially works.
map = new mapboxgl.Map({
container: 'mapdiv',
style: 'mapbox://styles/mapbox/streets-v10'
, center: start
, zoom: $scope.zoom
, transformRequest: (url, resourceType) => {
if (resourceType === 'Source' && url.startsWith(CONFIG.API_URL)) {
return {
headers: {
'Authorization': 'Bearer ' + localStorageService.get("authorizationData")
}
}
}
}
});
map.addSource(layerName,
{
"type": 'geojson',
"data": { "type": "FeatureCollection", "features": [] }
});
The tricky part is to know when to run the data retrieval call. The only place I could find was on the maps data event which now looks like:
map.on('data', function (e) {
if (e.dataType === 'source' && e.isSourceLoaded === false && e.tile === undefined) {
// See if the datasource is known
for (var i = 0; i < $scope.remLayers.length; i++) {
if (e.sourceId === $scope.remLayers[i].name) {
askForData(i)
}
}
}
});
function askForData(i) {
var data = getBBoxString(map);
var mapZoomLevel = map.getZoom();
if (checkZoom(mapZoomLevel, $scope.remLayers[i].minZoom, $scope.remLayers[i].maxZoom)) {
mapWebSvr.getData({
bbox: data, dtype: 0, id: $scope.remLayers[i].id, buffer: $scope.remLayers[i].isBuffer, sessionId
},
function (data, indexValue, indexType) {
showNewData(data, indexValue, indexType);
},
function () {
// Not done yet.
},
i,
0
);
}
}
function showNewData(ajxresponse, index, indexType) {
map.getSource($scope.remLayers[index].name).setData(ajxresponse);
map.getSource($scope.remLayers[index].name).isSourceLoaded = true;
}
This is all working with one exception. It keeps firing time and time again. Some of these calls return a lot of data for a web call so its not a solution at the moment.
Its like its never satisfied with the data even though its showing it on the map!
There is a parameter on the data event, isSourceLoaded but it does not get set to true.
I have searched for an example, have tried setting isSourceLoaded in a number of places (as with the code above) but to no avail.
Does anyone have a method accomplishing this basic data retrieval function successfully or can point out the error(s) in my code? Or even point me to a working example...
I have spent too long on this now and could do with some help.
After a bit of a run around I have a solution.
A Mapbox email pointed to populating the data in the load event - which I am now doing.
This was not however the solution I was looking for as the data needs refreshing when the map moves, zooms etc - further look ups are required.
Following a bit more a examination a solution was found.
Using the code blow on the render event will request the information when the bounding box is changed.
var renderStaticBounds = getBoundsString(map.getBounds());
map.on('render', function (e) {
if (renderStaticBounds != getBoundsString(map.getBounds())) {
renderStaticBounds = getBoundsString(map.getBounds());
for (var i = 0; i < $scope.remLayers.length; i++) {
askForData(i);
}
}
});
function getBoundsString(mapBounds) {
var left = mapBounds._sw.lng;
var bottom = mapBounds._sw.lat;
var right = mapBounds._ne.lng;
var top = mapBounds._ne.lat;
return left + ',' + bottom + ',' + right + ',' + top;
}
This hopefully will save someone some development time.
I am trying to make an ajax call using the enyo framework and I am running headlong in to a problem. The error message I am getting is 0. That's it just a 0. I made sure my link to the json file was correct and I built this jsfiddle to test it out http://jsfiddle.net/mmahon512/CPU8n/2/ Any help is greatly appreciated. My host is GoDaddy and I made sure that I added the json extension to my web config correctly. The link to the json file is correct and it returns valid json. I checked it using jsonlint. Here is what the code looks like on jsfiddle:
enyo.kind({
name: "AjaxSample",
components: [
{ kind: "Button", content: "Fetch Users", ontap: "fetch" },
{ name: "repos", content: "Not loaded...", allowHtml: true }
],
fetch: function() {
var ajax = new enyo.Ajax({
url: "http://atxapps.com/_sites/atxapps.com/dev/jetstream/assets/dataUsers.json"
});
ajax.go();
ajax.response(this, "gotResponse");
ajax.error(this, this.gotError);
},
gotResponse: function(inSender, inResponse) {
var output = "";
for(i = 0; i < inResponse.length; i++) {
output += inResponse[i].Id + "";
}
output += Date.now();
this.$.repos.setContent(output);
},
gotError: function(inSender, inError) {
alert(inError);
this.$.repos.setContent(inError + " " + Date.now());
}
});
Looks like a CORS issue. I see the following in the console:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin fiddle.jshell.net is therefore not allowed access.
I wrapped it as a jsonp request successfully.
http://jsfiddle.net/CPU8n/3/
enyo.kind({
name: "AjaxSample",
components: [
{ kind: "Button", content: "Fetch Users", ontap: "fetch" },
{ name: "repos", content: "Not loaded...", allowHtml: true }
],
fetch: function() {
var ajax = new enyo.JsonpRequest({
url: "http://jsonpwrapper.com/?urls%5B%5D=http%3A%2F%2Fatxapps.com%2F_sites%2Fatxapps.com%2Fdev%2Fjetstream%2Fassets%2FdataUsers.json"
});
ajax.go();
ajax.response(this, "gotResponse");
ajax.error(this, this.gotError);
},
gotResponse: function(inSender, inResponse) {
var output = "";
var body = enyo.json.parse(inResponse[0].body); // jsonpwrapper.com wraps the results in a array with an index for each URL. The actual data is in the body param of that index but it isn't parsed (at least in this example)
for(i = 0; i < body.length; i++) {
output += body[i].Id + "<br />";
}
output += Date.now();
this.$.repos.setContent(output);
},
gotError: function(inSender, inError) {
alert(inError);
this.$.repos.setContent(inError + " " + Date.now());
}
});
If you're running this on the same server in prod, you wouldn't see the error (since it's not cross-origin). If it'll be on a different server, you can either convert the server-side to support jsonp or adds the appropriate CORS headers.
I'm having an issue trying to directly upload a file to azure blob storage. I am using ajax calls to send post requests to an ashx handler to upload a blob in chunks. The issue I am running into is the handler isn't receiving the filechunk being sent from the ajax post.
I can see the page is receiving the post correctly from looking at the request in firebug,
-----------------------------265001916915724 Content-Disposition: form-data; >name="Slice"; filename="blob" Content-Type: application/octet-stream
I noticed the input stream on the handler has the filechunk, including additional bytes from the request. I tryed to read only the filechunk's size from the inputstream, however this resulted in an corrupt file.
I got the inspiration from http://code.msdn.microsoft.com/windowsazure/Silverlight-Azure-Blob-3b773e26 , I simply converted it from MVC3 to using standard aspx.
Here is the call using ajax to send the file chunk to the aspx page,
var sendFile = function (blockLength) {
var start = 0,
end = Math.min(blockLength, uploader.file.size),
incrimentalIdentifier = 1,
retryCount = 0,
sendNextChunk, fileChunk;
uploader.displayStatusMessage();
sendNextChunk = function () {
fileChunk = new FormData();
uploader.renderProgress(incrimentalIdentifier);
if (uploader.file.slice) {
fileChunk.append('Slice', uploader.file.slice(start, end));
}
else if (uploader.file.webkitSlice) {
fileChunk.append('Slice', uploader.file.webkitSlice(start, end));
}
else if (uploader.file.mozSlice) {
fileChunk.append('Slice', uploader.file.mozSlice(start, end));
}
else {
uploader.displayLabel(operationType.UNSUPPORTED_BROWSER);
return;
}
var testcode = 'http://localhost:56307/handler1.ashx?create=0&blockid=' + incrimentalIdentifier + '&filename=' + uploader.file.name + '&totalBlocks=' + uploader.totalBlocks;
jqxhr = $.ajax({
async: true,
url: testcode,
data: fileChunk,
contentType: false,
processData:false,
dataType: 'text json',
type: 'POST',
error: function (request, error) {
if (error !== 'abort' && retryCount < maxRetries) {
++retryCount;
setTimeout(sendNextChunk, retryAfterSeconds * 1000);
}
if (error === 'abort') {
uploader.displayLabel(operationType.CANCELLED);
uploader.resetControls();
uploader = null;
}
else {
if (retryCount === maxRetries) {
uploader.uploadError(request.responseText);
uploader.resetControls();
uploader = null;
}
else {
uploader.displayLabel(operationType.RESUME_UPLOAD);
}
}
return;
},
success: function (notice) {
if (notice.error || notice.isLastBlock) {
uploader.renderProgress(uploader.totalBlocks + 1);
uploader.displayStatusMessage(notice.message);
uploader.resetControls();
uploader = null;
return;
}
++incrimentalIdentifier;
start = (incrimentalIdentifier - 1) * blockLength;
end = Math.min(incrimentalIdentifier * blockLength, uploader.file.size);
retryCount = 0;
sendNextChunk();
}
});
};
Thanks so much for anything that can help me out.
is it ASPX on purpose? in http://localhost:56307/handler1.ashx?create=0&blockid?
Turns out on my webform, the input file tag was missing the enctype="multipart/form-data" attribute.
I've been pouring over this for hours and I've yet to make much headway so I was hoping one of the wonderful denizens of SO could help me out. Here's the problem...
I'm implementing a tree via the jstree plugin for jQuery. I'm pulling the data with which I populate the tree programatically from our webapp via json dumped into an asp:HiddenField, basically like this:
JavaScriptSerializer serializer = new JavaScriptSerializer();
string json = serializer.Serialize(Items);
json = json.ToLower();
data.Value = json;
Then, the tree pulls the json from the hidden field to build itself. This works perfectly fine up until I try to persist data for which nodes are selected/opened. To simplify my problem I've hardcoded some json data into the tree and attempted to use the cookie plugin to persist the tree state data. This does not work for whatever reason. I've seen other issues where people need to load the plugins in a specific order, etc, this did not solve my issue. I tried the same setup with html_data and it works perfectly. With this working persistence I converted the cookie plugin to persist the data in a different asp:hiddenfield (we can't use cookies for this type of thing in our application.)
essentially the cookie operations are identical, it just saves the array of nodes as the value of a hidden field. This works with the html_data, still not with the json and I have yet to be able to put my finger on where it's failing.
This is the jQuery.cookie.js replacement:
jQuery.persist = function(name, value) {
if (typeof value != 'undefined') { // name and value given, set persist
if (value === null) {
value = '';
}
jQuery('#' + name).attr('value', value);
} else { // only name given, get value
var persistValue = null;
persistValue = jQuery('#' + name).attr('value');
return persistValue;
}
};
The jstree.cookie.js code is identical save for a few variable name changes.
And this is my tree:
$(function() {
$("#demo1").jstree({
"json_data": {
"data" : [
{
"data" : "A node",
"children" : [ "Child 1", "Child 2" ]
},
{
"attr": { "id": "li.node.id" },
"data" : {
"title": "li.node.id",
"attr": { "href": "#" }
},
"children": ["Child 1", "Child 2"]
}
]
},
"persistence": {
"save_opened": "<%= open.ClientID %>",
"save_selected": "<%= select.ClientID %>",
"auto_save": true
},
"plugins": ["themes", "ui", "persistence", "json_data"]
});
});
The data -is- being stored appropriately in the hiddenfields, the problem occurs on a postback, it does not reopen the nodes. Any help would be greatly appreciated.
After looking through this some more, I just wanted to explain that it appears to me that the issue is that the tree has not yet been built from the JSON_data when the persistence operations are being attempted. Is there any way to postpone these actions until after the tree is fully loaded?
If anyone is still attempting to perform the same type of operation on a jsTree version 3.0+ there is an easier way to accomplish the same type of functionality, without editing any of the jsTree's core JavaScript, and without relying on the "state" plugin (Version 1.0 - "Persistence"):
var jsTreeControl = $("#jsTreeControl");
//Can be a "asp:HiddenField"
var stateJSONControl = $("#stateJSONControl");
var url = "exampleURL";
jsTreeControl.jstree({
'core': {
"data": function (node, cb) {
var thisVar = this;
//On the initial load, if the "state" already exists in the hidden value
//then simply use that rather than make a AJAX call
if (stateJSONControl.val() !== "" && node.id === "#") {
cb.call(thisVar, { d: JSON.parse(stateJSONControl.val()) });
}
else {
$.ajax({
type: "POST",
url: url,
async: true,
success: function (json) {
cb.call(thisVar, json);
},
contentType: "application/json; charset=utf-8",
dataType: "json"
}).responseText;
}
}
}
});
//If the user changes the jsTree, save the full JSON of the jsTree into the hidden value,
//this will then be restored on postback by the "data" function in the jsTree decleration
jsTreeControl.on("changed.jstree", function (e, data) {
if (typeof (data.node) != 'undefined') {
stateJSONControl.val(JSON.stringify(jsTreeControl.jstree(true).get_json()));
}
});
This code will create a jsTree and save it's "state" into a hidden value, then upon postback when the jsTree is recreated, it will use its old "state" restored from the "HiddenField" rather than make a new AJAX call and lose the expansions/selections that the user has made.
Got it working properly with JSON data. I had to edit the "reopen" and "reselect" functions inside jstree itself.
Here's the new functioning reopen function for anyone who needs it.
reopen: function(is_callback) {
var _this = this,
done = true,
current = [],
remaining = [];
if (!is_callback) { this.data.core.reopen = false; this.data.core.refreshing = true; }
if (this.data.core.to_open.length) {
$.each(this.data.core.to_open, function(i, val) {
val = val.replace(/^#/, "")
if (val == "#") { return true; }
if ($(("li[id=" + val + "]")).length && $(("li[id=" + val + "]")).is(".jstree-closed")) { current.push($(("li[id=" + val + "]"))); }
else { remaining.push(val); }
});
if (current.length) {
this.data.core.to_open = remaining;
$.each(current, function(i, val) {
_this.open_node(val, function() { _this.reopen(true); }, true);
});
done = false;
}
}
if (done) {
// TODO: find a more elegant approach to syncronizing returning requests
if (this.data.core.reopen) { clearTimeout(this.data.core.reopen); }
this.data.core.reopen = setTimeout(function() { _this.__callback({}, _this); }, 50);
this.data.core.refreshing = false;
}
},
The problem was that it was trying to find the element by a custom attribute. It was just pushing these strings into the array to search when it was expecting node objects. Using this line
if ($(("li[id=" + val + "]")).length && $(("li[id=" + val + "]")).is(".jstree-closed")) { current.push($(("li[id=" + val + "]"))); }
instead of
if ($(val).length && $(val).is(".jstree-closed")) { current.push(val); }
was all it took. Using a similar process I was able to persist the selected nodes this way as well.
Hope this is of help to someone.