I don't understand how to get a token to make API calls - paypal

I'm using the Payouts API and to do so I have to specify an access token when calling https://api-m.sandbox.paypal.com/v1/payments/payouts
To get an access token, as specified here I have to send a POST request with Postman to https://developer.paypal.com/docs/api/overview/#get-an-access-token and it returns in the response the token
My problem is that this token has an expiration time, which is an issue because I can't go in my app every 15 minutes to change the token in the Authorization header. How can I get a "permanent" or rather an "auto refreshed" token ?
I tried to make a call from my app instead of Postman but it doesn't seem to work, it says my credentials are invalid
This is my code in case it can be useful (I'm managing everything from the front-end):
window.paypal
.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
description: this.product.description,
amount: {
value: this.product.price
}
}
]
});
},
onApprove: (data, actions) => {
const timestamp = new Date().getUTCMilliseconds();
const id = timestamp;
return actions.order.capture().then(details => {
axios
.post(
"https://api-m.sandbox.paypal.com/v1/payments/payouts",
{
sender_batch_header: {
sender_batch_id: id,
email_subject: "You have a payout!",
email_message:
"You have received a payout! Thanks for using our service!",
recipient_type: "EMAIL"
},
items: [
{
amount: {
value: this.product.price,
currency: "USD"
},
note: "congrats someone bought your stuff",
sender_item_id: id,
receiver: "john#doe.com"
}
]
},
{
headers: {
Authorization:
"Bearer <my token that I get from postman>"
}
}
)
.then(() => {
alert(
"Transaction completed by " + details.payer.name.given_name
);
})
.catch(err => console.log(err));
});
}
})
.render(".paypal");
}
},

Update
Reading the code a second time and noticing how your Payout is for the same this.product.price as the original payment, that makes no sense. Don't use payouts at all for this use case. The full amount will go to your account normally, but if you want the full amount to go somewhere else instead, set the payee variable for this checkout: https://developer.paypal.com/docs/checkout/integration-features/pay-another-account/
(original answer below)
Using Payouts from the client side is an absolutely terrible idea, anyone with your client credentials will be able to send money from your account to wherever they want
The payouts API must be used from your server only. Your server needs to do an API call to get an access token, similar to what you mention about postman -- except using whatever backend environment you have. You can integrate direct HTTPS calls, or there are also Payouts SDKs available which will handle getting the access token automatically.
As for how to trigger that backend action when capturing a payment, use a proper server integration:
Create two routes, one for 'Create Order' and one for 'Capture Order', documented here. These routes should return JSON data. Before returning JSON data, that last route (the capture one) should record the successful transaction in your database and trigger any Payout logic you want.
Pair those two routes with the following approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server

Related

How to cache individual resources when returning a collection of that resource?

I'm sure that this must be either a well solved problem, or there's a good reason why it's not a good idea, but my google-fu is failing me here.
Say I have an express endpoint like this:
const allUsers = [
{
id: "1",
name: "Alice Smith"
},
{
id: "2",
name: "Bob Smith"
}
];
app.get("/users", (req, res) => {
res.setHeader("Cache-Control", "max-age=60");
res.status(200).send(allUsers)
});
This works fine, when my browser attempts the refetch the /users endpoint within the 60 seconds, it just uses the browser cache.
The problem is, say I have an endpoint for an individual user:
app.get("/users/:id", (req, res) => {
const id = req.params.id;
const user = allUsers.find((v) => v.id === id);
res.setHeader("Cache-Control", "max-age=60");
res.status(200).send(user)
});
Sure, this will similarly cache results for subsequent calls that particular endpoint.
However, what I really want to do is have the server tell the browser, when the browser does that first fetch of /users to, 'Hey, also cache /users/1 and /users/2, destructure the response body to work out the values'.
Is this in anyway possible, or has such a mechanism ever been proposed?
Is there any mechanism within a browser, to programmatically set endpoint caches, without actually calling the endpoint?

Missing conversationId for making Transactions on the new Actions SDK

I'm trying to POST a request to https://actions.googleapis.com/v3/packages/{packageName}/skus:batchGet as described in section 2. b. in the non-consumable digital transactions guide. Pasting the relevant snippet here:
return jwtClient.authorize((err, tokens) => {
if (err) {
throw new Error(`Auth error: ${err}`);
}
const packageName = 'com.example.projectname';
request.post(`https://actions.googleapis.com/v3/packages/${packageName}/skus:batchGet`, {
'auth': {
'bearer': tokens.access_token,
},
'json': true,
'body': {
'conversationId': conversationId,
'skuType': 'APP',
// This request is filtered to only retrieve SKUs for the following product IDs
'ids': ['nonconsumable.1']
},
}, (err, httpResponse, body) => {
if (err) {
throw new Error(`API request error: ${err}`);
}
console.log(`${httpResponse.statusCode}: ${httpResponse.statusMessage}`);
console.log(JSON.stringify(body));
});
});
});
The request body should have a conversationId field. While this field exists in the Dialogflow and legacy Actions SDK, it's missing from the new Actions SDK webhook requests as far as I can tell.
The new Actions SDK documentation links to that digital transactions guide so I have assumed it should be compatible, but have found no mention of required adaptations to be able to use it.
So my question is, how can that conversationId be fetched when making transactions from a webhook fulfilling requests from the new Actions SDK?
The snippet provided in the documentation is incorrect.
Please use the session ID. You can access this value via conv.session.id.

Get all organizations in Azure DevOps using REST API

I am trying to retrieve all the organizations in my account but in the documentation an organization is always required in the API call.
https://dev.azure.com/{organization}/_apis/...
If you load the current landing page, it displays all your organizations tied to your account. I assumed it had to get that information some way. I captured the network traffic and I believe you could get to the data you want using a system API call. However, it might change or might become unsupported without notice, so use at your own discretion.
You can get the information you want using this API:
Post https://dev.azure.com/{organization1}/_apis/Contribution/HierarchyQuery?api-version=5.0-preview.1
Body:
{
"contributionIds": ["ms.vss-features.my-organizations-data-provider"],
"dataProviderContext":
{
"properties":{}
}
}
Response:
{
"dataProviderSharedData": {},
"dataProviders": {
"ms.vss-web.component-data": {},
"ms.vss-web.shared-data": null,
"ms.vss-features.my-organizations-data-provider": {
"organizations": [
{
"id": "{redacted id}",
"name": "{organization1}",
"url": "https://{organization1}.visualstudio.com/"
},
{
"id": "{redacted id}",
"name": "{organization2}",
"url": "https://dev.azure.com/{organization2}/"
}
],
"createNewOrgUrl": "https://app.vsaex.visualstudio.com/go/signup?account=true"
}
} }
you can do it simply by making a call to get all the account you are member/ owner of. However for that you need your id, which can be easily fetched by making get profile call. Here are steps below:
Make a VSTS API call to get profile details using Bearer token or PAT
https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=5.1
This will return you, your Id:
{
"displayName": "xxxx",
"publicAlias": "xxx",
"emailAddress": "xxx",
"coreRevision": xxx,
"timeStamp": "2019-06-17T09:29:11.1917804+00:00",
"id": "{{We need this}}",
"revision": 298459751
}
Next, make a call to get all the accounts you are member of or owner of:
https://app.vssps.visualstudio.com/_apis/accounts?api-version=5.1&memberId={{Your Id}}
Response:
{
"count": 1,
"value": [
{
"accountId": "xxx",
"accountUri": "xxx",
"accountName": "xxx",
"properties": {}
}
]
}
It will return list of accounts you are associated with.
A REST API request/response pair can be separated into five components:
The request URI, in the following form:
VERB https://{instance}[/{team-project}]/_apis[/{area}]/{resource}?api-version={version}
instance:
The Azure DevOps Services organization or TFS server you're sending the request to.
They are structured as follows:
Azure DevOps Services: dev.azure.com/{organization}
The REST API's are organization specific. This is not documented at present. You could submit a feature request here: https://developercommunity.visualstudio.com/spaces/21/index.html
Our PM and product team will kindly review your suggestion. Sorry for any inconvenience.
As a workaround, you could use the API which captured from network traffic just as Matt mentioned.
We've been using "https://app.vssps.visualstudio.com/_apis/accounts" without specifying any API version and this returns all our accountnames
This is still working for us, but because of some other issues we have I'm adding the api version to all our api calls, however. For this I also run into the fact that https://learn.microsoft.com/en-us/rest/api/azure/devops/account/accounts/list?view=azure-devops-rest-5.0 requires an member or owner id.
Retrieving that needs an account/organization so it is a bit of a catch 22 situation.
For now I'll stay with just "https://app.vssps.visualstudio.com/_apis/accounts" I guess
I'm getting a sign-in response for both "app.vssps.visualstudio.com/_apis/accounts" and
Post https://dev.azure.com/{organization1}/_apis/Contribution/HierarchyQuery?api-version=5.0-preview.1
StatusCode : 203
StatusDescription : Non-Authoritative Information
EDIT:
Nevermind, it worked using the static MSA clientid and replyURL:
internal const string clientId = "872cd9fa-d31f-45e0-9eab-6e460a02d1f1"; //change to your app registration's Application ID, unless you are an MSA backed account
internal const string replyUri = "urn:ietf:wg:oauth:2.0:oob"; //change to your app registration's reply URI, unless you are an MSA backed account
//PromptBehavior.RefreshSession will enforce an authn prompt every time. NOTE: Auto will take your windows login state if possible
result = ctx.AcquireTokenAsync(azureDevOpsResourceId, clientId, new Uri(replyUri), promptBehavior).Result;
Console.WriteLine("Token expires on: " + result.ExpiresOn);
var bearerAuthHeader = new AuthenticationHeaderValue("Bearer", result.AccessToken);
// Headers
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("User-Agent", "ManagedClientConsoleAppSample");
client.DefaultRequestHeaders.Add("X-TFS-FedAuthRedirect", "Suppress");
client.DefaultRequestHeaders.Authorization = authHeader;
//Get Organizations
client.BaseAddress = new Uri("https://app.vssps.visualstudio.com/");
HttpResponseMessage response1 = client.GetAsync("_apis/accounts").Result;

When `actions.intent.TRANSACTION_REQUIREMENTS_CHECK` returns result different than `OK`?

I want to create a chatbot with Dialogflow and Google Assistant along with Google Transactions API for enabling a user to order a chocolate box. For now my agent contains the following four intents:
Default Welcome Intent (text response: Hello, do you want to buy a chocolate box?)
Default Fallback Intent
Int1 (training phrase: Yes, I want, fulfilment: enabled webhook call)
Int2 (event: actions_intent_TRANSACTION_REQUIREMENTS_CHECK )
I am using Dialogflow Json instead of Node.js to connect my agent with Transactions API. I want to test that the user meets the transaction requirements (when ordering the chocolate box) by using the actions.intent.TRANSACTION_REQUIREMENTS_CHECK action of Google actions. For this reason, following Google docs, when Int1 is triggered I am using a webhook which connects Google Assistant to the following python script (back-end):
from flask import Flask, render_template, request, jsonify
from flask_cors import CORS
import requests
app = Flask(__name__)
CORS(app)
#app.route("/", methods=['POST'])
def index():
data = request.get_json()
intent = data["queryResult"]["intent"]["displayName"]
if (intent == 'Int1'):
return jsonify({ "data": {
"google": {
"expectUserResponse": True,
"isSsml": False,
"noInputPrompts": [],
"systemIntent": {
"data": {
"#type": "type.googleapis.com/google.actions.v2.TransactionRequirementsCheckSpec",
"paymentOptions": {
"actionProvidedOptions": {
"displayName": "VISA-1234",
"paymentType": "PAYMENT_CARD"
}
}
},
"intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK"
}
}
}
})
else:
return jsonify({'message': 'HERE'})
if __name__== "__main__":
app.run(debug=True)
The result in the json response which I receive after actions.intent.TRANSACTION_REQUIREMENTS_CHECK and Int2 are triggered is:
"arguments": [
{
"extension": {
"#type": "type.googleapis.com/google.actions.v2.TransactionRequirementsCheckResult",
"resultType": "OK"
},
"name": "TRANSACTION_REQUIREMENTS_CHECK_RESULT"
}
]
The confusing fact is that even if I send:
{
"displayName": "FALSE",
"paymentType": "PAYMENT_CARD"
}
the response is the same which means that it returns again OK.
When I send something like this
{
"displayName": "FALSE",
"paymentType": "WRONG"
}
then I get an error:
API Version 2: Failed to parse JSON response string with 'INVALID_ARGUMENT' error: "(payment_options.action_provided_options.payment_type): invalid value "WRONG" for type TYPE_ENUM".
but this is not exactly given by actions.intent.TRANSACTION_REQUIREMENTS_CHECK and Int2 because these two are not triggered so I do not get any json response back with a result different than OK.
Therefore, my question is: In which cases am I going to receive a result from actions.intent.TRANSACTION_REQUIREMENTS_CHECK which is different than OK?
If I am going to get an OK result for anything that I am writing then what is the point of using actions.intent.TRANSACTION_REQUIREMENTS_CHECK?
P.S.
I have in mind the following about actions.intent.TRANSACTION_REQUIREMENTS_CHECK from Google docs:
Note: The actions.intent.TRANSACTION_REQUIREMENTS_CHECK intent is
currently under development and will return a success state regardless
of the user's payment settings and locale. To test out the failure
state scenario, request the intent on a voice-activated speaker.
but still I am not seeing any difference when I using this app on Google Assistant with my voice on my mobile phone.
I think that you we have to return to Google docs to solve this. According to Google docs the possible responses of actions.intent.TRANSACTION_REQUIREMENTS_CHECK are the following: RESULT_TYPE_UNSPECIFIED, OK, USER_ACTION_REQUIRED, ASSISTANT_SURFACE_NOT_SUPPORTED, REGION_NOT_SUPPORTED
(image).
Nothing of them has to do exactly with this:
"paymentOptions": {
"actionProvidedOptions": {
"displayName": "VISA-1234",
"paymentType": "PAYMENT_CARD"
}
}
This is also because your back-end cannot (or it is not even allowed to) directly reach the payment details of the user so the json above is only inserted by your back-end if you know them or in a sense you can write whatever you want. This is only for being displayed at the order preview and it is not cross-checked with the payment details of the user's Google account.
In conclusion, actions.intent.TRANSACTION_REQUIREMENTS_CHECK may only return a non OK status if the result is unspecified (RESULT_TYPE_UNSPECIFIED) or if the user is expected to take action (USER_ACTION_REQUIRED) or if the transactions are not supported on current device/surface (ASSISTANT_SURFACE_NOT_SUPPORTED) or if the transactions are not supported for current region/country (REGION_NOT_SUPPORTED).

Paypal error message "Capture amount exceeds allowable limit"

We wrote the below function in sandbox environment
function captureTransactions(access_token, d) {
try {
var req = http.request("POST", _config.host + "/v1/payments/authorization/" + d.authorization_id + "/capture", JSON.stringify({
"amount": {
"currency": "USD",
"total": d.total
},
"is_final_capture": true
}), {
"Content-Type": "application/json",
"Authorization": "Bearer " + access_token
});
var r = JSON.parse(req.readAll().toString());
} catch (e) {
console.error(e.stack);
console.error(r);
}
console.warn("[captureTransactions]", r);
return r;
};
and receive the below error
{
"name": "CAPTURE_AMOUNT_LIMIT_EXCEEDED",
"message": "Capture amount exceeds allowable limit.",
"information_link": "https://developer.paypal.com/webapps/developer/docs/api/#CAPTURE_AMOUNT_LIMIT_EXCEEDED",
"debug_id": "2fc79fc7df623"
}
Can anyone tell me what is the reason for receiving this msg and how to fix it? Note that we only test $5 payments so it shouldn't be due to transaction size. We did do multiple authorizations on the same card before, but not sure if that could be the reason for this error?
Appreciate your help!
I have had this problem on the live site with PayFlow today. It appears that you cannot use a "Credit Card" that is "Attached" to that "Gateway" account with "PayPal".
I supposed that is a method to stop "Customers" (PayPal payment service users) from "Paying" themselves as a Paypal employee explained to me on another separate issue. That is where we were testing a Billing Software with the gateway and refunding the money back to our account after the transaction. We were told that we could put our own money into our account(s) that way. At the time "I ask how we were to pay the $60.00 monthly fee if the business (PayPal account) hasn't collected/processed any money yet if we can't put money in there? This was resolved by the 1st part of this Answer.