Closed sunrunner4kr-simon closed 3 years ago
Hey, you don't need to use ebay-oauth-nodejs-client. The auth is also implemented in this lib.
If you just want to try out, you can generate a token here: https://developer.ebay.com/my/auth?env=production&index=0&auth_type=oauth
(Production)
const eBay = new eBayApi({
appId: "######################",
certId: "######################",
sandbox: false,
scope: [
'https://api.ebay.com/oauth/api_scope',
'https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly',
'https://api.ebay.com/oauth/api_scope/sell.fulfillment'
],
siteId: eBayApi.SiteId.EBAY_UK
});
eBay.OAuth2.setCredentials({
refresh_token: '',
expires_in: 7200,
refresh_token_expires_in: 7200,
token_type: '',
access_token: 'v^1.1#i............................PLACE THIS LONG TOKEN HERE'
})
eBay.sell.fulfillment
.getOrders()
.then((orders) => {
console.log(JSON.stringify(orders, null, 2));
})
.catch((e) => {
console.log(e);
});
This is the way how to generate this token:
// 1. Create new eBayApi instance and set the scope.
const eBay = new eBayApi({
appId: "######################",
certId: "######################",
sandbox: false,
scope: [
'https://api.ebay.com/oauth/api_scope',
'https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly',
'https://api.ebay.com/oauth/api_scope/sell.fulfillment'
],
// optional parameters, should be omitted if not used
siteId: eBayApi.SiteId.EBAY_UK
});
const url = eBay.OAuth2.generateAuthUrl();
// 2. Open Url and Grant Access
console.log('Open URL', url);
// 3. Get the code that is placed as query parameter in redirected page
const code = 'code'; // from www.your-website?code=XXXX
// 4. Get the token
(async () => {
// Use async/await
const token = await eBay.OAuth2.getToken(code);
eBay.OAuth2.setCredentials(token);
eBay.sell.fulfillment.getOrders().then(order => {
console.log('order', JSON.stringify(order, null, 2));
}).catch(e => {
console.log('error', {error: e.message});
});
})();
Thanks for the help!
It was the 'code' part that through me off with your function. I don't understand what that is, or where I get it from?
https://developer.ebay.com/my/auth?env=production&index=0&auth_type=oauth
Here you can generate this access token. Click on the blue button "Sign in to production for OAuth" button.
This will generate this access token.
Sorry @dantio I meant this bit: -
// 3. Get the code that is placed as query parameter in redirected page
const code = 'code'; // from www.your-website?code=XXXX
eBay will redirect you to the URL you setup in your settings and add a query code
to this url.
Did you setup the redirect URL correctly? It must be a https site.
Do you mean from the eBay Token settings?
I'm trying to test in the sandbox first with my auth settings.
So I set up the below: -
Ok, so there's 2 ways to get auth. I had the credentials grant flow working with ebay-oauth-nodejs-client, whereas you're using the auth code grant flow...https://developer.ebay.com/api-docs/static/oauth-authorization-code-grant.html
I don't understand yet how to manage the redirect url etc...
So I tried passing my auth token (from the credentials flow) directly to your eBay.OAuth2.setCredentials
And now I'm passed that, I'm getting an invalid scope error: -
error {
error: 'invalid_scope: The requested scope is invalid, unknown, malformed, or exceeds the scope granted to the client'
}
So...not sure if my token worked and there's another problem, or the scope is invalid because the token didn't work XD
OK, if I comment out the sell.fulfillment scope, I get the Access denied error...so it is probably my token.
error {
error: 'Access denied: Insufficient permissions to fulfill the request.'
}
So, I guess .generateAuthUrl is getting the 'Grant Application Access' page. Which I'm getting: -
Open URL https://auth.sandbox.ebay.com/oauth2/authorize?client_id=SimonEdg-StoreApp-SBX-acbb713f2-79a757fb&redirect_uri=Simon_Edge-SimonEdg-StoreA-rcrfx&response_type=code&state=&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope
And then a user is meant to grant consent...but there's no user involved here, it's just a process getting the code so we can swap for a token. How does the grant access bit work?
Sorry for the annoying questions...and I appreciate the help!
As I said before, you don't need to use ebay-oauth-nodejs-client
.
The getApplicationToken('PRODUCTION')
is the same as eBay.OAuth2.getClientAccessToken()
. And this is called automatically in this library (if not token is set). However, the client token does not have enough permission to give you access to fulfillments.
Let's do step by step.
Get a User Token Here
Sign in into Sandbox for OAuth
Buttonaccess_token
(it already set all Scope)Copy the access_token
eBay.OAuth2.setCredentials({
refresh_token: '',
expires_in: 7200,
refresh_token_expires_in: 7200,
token_type: '',
access_token: 'v^1.1#i............................PLACE THIS LONG TOKEN HERE'
})
eBay.sell.fulfillment
.getOrders()
.then((orders) => {
console.log(JSON.stringify(orders, null, 2));
})
This should work!
Since this token will expire and can'tbe refreshed and you don't want to do this again, let's setup the 'redirect URL'.
Take a look in your screenshot. The 'redirect URL' is set in Your auth accepted URL
.
And the ruName
is Simon_Edge-SimonEdg-Store-etc
(you need to define this in new eBayApi(..)
)
So you need a working webserver that provides this endpoint (in https)! You can use something like https://ngrok.com/ to run it locally.
Now follow :
const url = eBay.OAuth2.generateAuthUrl();
// 2. Open Url and Grant Access
console.log('Open URL', url);
// 3. Get the code that is placed as query parameter in redirected page
// !!! EBAY WILL REDIRECT YOU BACK TO YOUR WEBSITE that is defined in "Your auth accepted URL"
const code = 'code'; // from www.your-website?code=XXXX
// 4. Get the token
(async () => {
// Use async/await
const token = await eBay.OAuth2.getToken(code);
eBay.OAuth2.setCredentials(token);
eBay.sell.fulfillment.getOrders().then(order => {
console.log('order', JSON.stringify(order, null, 2));
}).catch(e => {
console.log('error', {error: e.message});
});
})();```
Thanks for your patience!
So my next js app is deployed in Vercel at https://wineo.me I set my auth accepted url to that: - 'https://wineo.me'
Here's my code...with the ruName set: -
const eBay = new eBayApi({
appId: "SimonEdg-StoreApp-xxxxxxx",
certId: "SBX-xxxxxxx",
sandbox: true,
scope: [
"https://api.ebay.com/oauth/api_scope",
"https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly",
"https://api.ebay.com/oauth/api_scope/sell.fulfillment",
],
// optional parameters, should be omitted if not used
siteId: eBayApi.SiteId.EBAY_UK, // required for traditional APIs, see https://developer.ebay.com/DevZone/merchandising/docs/Concepts/SiteIDToGlobalID.html
ruName: "Simon_Edge-SimonEdg-StoreA-rcrfx", // required for authorization code grant
//authToken: "", // can be set to use traditional API without code grant
});
const url = eBay.OAuth2.generateAuthUrl();
// 2. Open Url and Grant Access
console.log("Open URL", url);
// 3. Get the code that is placed as query parameter in redirected page
const code = "code"; // from www.your-website?code=XXXX
// 4. Get the token
(async () => {
// Use async/await
const token = await eBay.OAuth2.getToken(code);
eBay.OAuth2.setCredentials(token);
eBay.sell.fulfillment
.getOrders()
.then((order) => {
console.log("order", JSON.stringify(order, null, 2));
})
.catch((e) => {
console.log("error", { error: e.message });
});
})();
with this I get a 400 error: -
2021-04-18T10:24:12.419Z f4dd890f-0d32-4fe5-a4dc-e2752979840c ERROR Unhandled Promise Rejection {"errorType":"Runtime.UnhandledPromiseRejection","errorMessage":"Error: Request failed with status code 400","reason":{"message":"Request failed with status code 400","name":"Error","stack":"Error: Request failed with status code 400\n at createError (/var/task/nextjs-store/node_modules/axios/lib/core/createError.js:16:15)\n at settle (/var/task/nextjs-store/node_modules/axios/lib/core/settle.js:17:12)\n at IncomingMessage.handleStreamEnd (/var/task/nextjs-store/node_modules/axios/lib/adapters/http.js:260:11)\n at IncomingMessage.emit (events.js:327:22)\n at endReadableNT (internal/streams/readable.js:1327:12)\n at processTicksAndRejections (internal/process/task_queues.js:80:21)","config":{"url":"https://api.sandbox.ebay.com/identity/v1/oauth2/token","method":"post","data":"grant_type=authorization_code&code=code&redirect_uri=Simon_Edge-SimonEdg-StoreA-rcrfx","headers":{"Accept":"application/json, text/plain, /","Content-Type":"application/x-www-form-urlencoded","Access-Control-Allow-Origin":"*","Access-Control-Allow-Headers":"X-Requested-With, Origin, Content-Type, X-Auth-Token","Access-Control-Allow-Methods":"GET, PUT, POST, DELETE","User-Agent":"axios/0.21.1","Content-Length":85},"auth":{"username":"SimonEdg-StoreApp-SBX-xxxxxxx","password":"SBX-xxxxxxxx"},"transformRequest":[null],"transformResponse":[null],"timeout":0,"xsrfCookieName":"XSRF-TOKEN","xsrfHeaderName":"X-XSRF-TOKEN","maxContentLength":-1,"maxBodyLength":-1}},"promise":{},"stack":["Runtime.UnhandledPromiseRejection: Error: Request failed with status code 400"," at process.
So I tried your suggested test scenario and created a user token manually. And that at least works :D
The above auth request is being called from /orders so not sure if it should redirect to 'https://wineo.me/orders' but I tried setting that in case, and that doesn't work either.
await
in try/catch code
if you use express
app.get('/success', async function(req, res) {
// Access the provided 'page' and 'limt' query parameters
const code = req.query.code; // this is provided from eBay
try {
const token = await eBay.OAuth2.getToken(code);
eBay.OAuth2.setCredentials(token);
const orders = await eBay.sell.fulfillment.getOrders()
res.send(orders);
} catch(e) {
console.error(e)
res.sendStatus(400)
}
});
Man I'm confused...
So my /orders defines the eBayApi entity and generates the auth url, storing in eBayApi object (this is where I want the orders data)
When visiting that url, ebay redirects to /success with a code, which /success uses to get the token and with that gets the orders.
I don't use express :/ So had a stab with the below
export default async (req, res) => {
// Access the provided 'page' and 'limt' query parameters
const code = req.query.code; // this is provided from eBay
try {
const token = await eBay.OAuth2.getToken(code);
eBay.OAuth2.setCredentials(token);
const orders = await eBay.sell.fulfillment.getOrders();
res.send(orders);
} catch (e) {
console.error(e);
res.sendStatus(400);
}
};
But where am I calling the auth url? does it visit the url in generateAuthUrl ? And if it does, where do the results of visiting /success get stored ?
Agree, It is confusing :)
So this would be flow example:
const url = eBay.OAuth2.generateAuthUrl();
<a href="${url}">Login</a>
code
and calls const token = await eBay.OAuth2.getToken(code);
eBay.OAuth2.setCredentials(token);
getOrders()
method :)I hope it helps
What do you use for routing?
Thanks!
Ah, I think this is where my problem is. I don’t want a user to log in to get their orders. What I need my app to do, is get the orders for a shop. So i wanted to set up a process in the app to get’s the orders and inventory and I was going to use the notifications API to get notifications of when new orders come in. Which is why i originally tried the Client Credentials flow...although I’ve now learnt that doesn’t work for the Fulfilment API.
So for me, the user would need to be the shop owner. But I wouldn’t want them to have to log in to eBay each time they use the app. And it wouldn’t work then when any other user logs in to the app (works in the shop) but doesn’t have the eBay shop user account.
Ideally I’d want a way to get this data in the background. But that doesn’t seem possible with the auth code flow.
So far I’ve only really had to use next/link to get around
The user only needs to login once you can store the token in db. And if you have the token you can fetch the orders in the background.
If you plan to have multiple ebay user, you have to use this way because every ebay user needs to authorize your application. After 2 hours the token expires and it will be refreshed (automatically) this new token needs to stored in db.
If you only want to do it for a single shop, you could use the AuthNauth token and the old traditional api. It also has getorders and the token is valid for 2 years or so.
Hmm..ok. The app will have multiple users, but local to the app. It’s only one eBay shop account that needs authorising. I might make a link in a settings page for that one user to log in to eBay to grant access. Then in the background it can be getting the data and refreshing automatically, without them ever having to do it again. And if something does go wrong, they can just log in and grant access again from the settings.
I’ll only pull the data when they visit /orders so the token will mostly likely going expire a lot of the time. So when it tries again, and gets the ‘invalid access token’ error, it will automatically refresh? How does that work, do I need to catch the error in the getOrders call? Or will it hit /success url redirect with a new code?
Yes that's sound good!
eBay.OAuth2.on('refreshAuthToken', (token) => { console.log(token) // Here you store this new token on database });
Great! Thanks for all the help. I’ll give it a go now :)
I'm still trying to test the above, but I have an unrelated prisma.io bug. Had to raise a bug report :( https://github.com/prisma/prisma/issues/6730
Working like a charm!! Thanks for all the help
Hi @sunrunner4kr-simon - and thanks @dantio for the lib. I want my express app to communicate with ebay. I have generated a user token at https://developer.ebay.com/my/auth/?env=sandbox&index=0&auth_type=oauth and have used this in as ebayAuthKey from Get a User Token Here
ebay.OAuth2.setCredentials({ refresh_token: '', expires_in: 7200, refresh_token_expires_in: 7200, token_type: '', access_token: ebayAuthKey })
How are the credentials updated i.e. can you use ebay.OAuth2.on('refreshAuthToken', (token) => { console.log(token) // Here you store this new token on database // add refresh token here? });
@sunrunner4kr-simon - can you share your flow please
Many thanks Gary
I use Prisma Gary, so if my request fails and hits the catch i refresh the token and store in my db
const token = await prisma.variable.findFirst({
where: {
variable: "ebayToken",
},
});
if (token) {
eBay.OAuth2.setCredentials(token.token);
eBay.sell.fulfillment
.getOrders()
.then((order) => {
console.log("order", JSON.stringify(order, null, 2));
})
.catch((e) => {
console.log("error", { error: e.message });
//const error_message = { error: e.message };
eBay.OAuth2.on("refreshAuthToken", (token) => {
console.log("Refresh token:", token);
async () => {
const result = await prisma.env_variable.upsert({
where: {
variable: "ebayToken",
},
update: { token: token },
create: {
variable: "ebayToken",
token: token,
},
});
if (result) {
console.log("Refresh Token Stored in DB");
} else console.log("Failed to store refresh Token");
};
})();
});
}
my DB token field is JSON
I haven't been running it long enough to see if the token is refreshed, but I'm now succesfully getting responses from ebay
OK...so the token has timed out, and I get an error in my request (as expected)
error {
error: 'invalid_scope: The requested scope is invalid, unknown, malformed, or exceeds the scope granted to the client'
}
But it's not refreshing the token, in fact i don't know if it's doing anything. from here onwards as it's not logging any of my console logs from the refreshfunction onwards. Like it's skipping the whole of the below.
eBay.OAuth2.on("refreshAuthToken", (token) => {
console.log("Refresh token:", token);
async () => {
const result = await prisma.env_variable.upsert({
where: {
variable: "ebayToken",
},
update: { token: token },
create: {
variable: "ebayToken",
token: token,
},
});
if (result) {
console.log("Refresh Token Stored in DB");
} else console.log("Failed to store refresh Token");
};
})();
If I manually request auth again, then I can get a new token.
Sorry @dantio so close to getting this working.
I now have it calling the refreshAuthToken function in the catch, but it's not getting a valid token.
For example, here is my code: -
.catch((e) => {
console.log("error", { error: e.message });
//const error_message = { error: e.message };
eBay.OAuth2.on("refreshAuthToken", (token) => {
console.log("Refresh token:", token);
updateToken(token);
});
})();
that console log isn't called...which I don't understand, but it does call the updateToken function: -
async function updateToken(token) {
console.log("Refresh token:", token);
let response = await prisma.env_variable.upsert({
where: {
variable: "ebayToken",
},
update: { token: token },
create: {
variable: "ebayToken",
token: token,
},
});
if (response) {
console.log("Token Refreshed: ", response);
//throw new Error(`Prisma Error: ${response}`);
}
}
updateToken().catch((e) => {
console.error("updateToken" + e.message);
});
but token isn't defined. So my question is, eBay.OAuth2.on "refreshAuthToken" stores the new token in the (token) variable right? But it's coming back undefined
In your readme, it says it will refresh when we get a 'invalid access token' error, but I'm getting the below error when it expires: -
'invalid_scope: The requested scope is invalid, unknown, malformed, or exceeds the scope granted to the client'
eBay.OAuth2.on("refreshAuthToken", (token) => {
console.log("Refresh token:", token);
updateToken(token);
});
Should not be used in catch
block instead move it before you call any api method.
eBay.OAuth2.setCredentials(token.token);
eBay.OAuth2.on("refreshAuthToken", (token) => {
console.log("Refresh token:", token);
updateToken(token);
});
// if the token is expired, it will be refreshed and 'refreshAuthToken' is fired
eBay.sell.fulfillment.getOrders().then((order) => {
console.log("order", JSON.stringify(order, null, 2));
})
// if you put it in catch block, it's already too late
Hmmm...so it also annoyingly gives an error, even when the request is successful. For example I call SetNotificationPreferences in the Trading API and get a successfully response (in fact i get a successful respone twice :/ ): -
Set Pref {
Timestamp: '2021-04-28T10:00:45.539Z',
Ack: 'Success',
Version: 1173,
Build: 'E1173_CORE_APINOTIFY_19146596_R1'
}
Set Pref {
Timestamp: '2021-04-28T10:00:45.646Z',
Ack: 'Success',
Version: 1173,
Build: 'E1173_CORE_APINOTIFY_19146596_R1'
}
But it falls in to the catch with the error: -
TypeError: eBay.trading.SetNotificationPreferences(...).then(...).catch(...) is not a function
at exports.modules../pages/api/ebay/getPreferences.js.__webpack_exports__.default (D:\Web\StoreApp\nextjs-store\.next\server\pages\api\ebay\getPreferences.js:209:11)
at runMicrotasks (<anonymous>)
at processTicksAndRejections (node:internal/process/task_queues:94:5)
at async apiResolver (D:\Web\StoreApp\nextjs-store\node_modules\next\dist\next-server\server\api-utils.js:8:1)
at async DevServer.handleApiRequest (D:\Web\StoreApp\nextjs-store\node_modules\next\dist\next-server\server\next-server.js:67:462)
at async Object.fn (D:\Web\StoreApp\nextjs-store\node_modules\next\dist\next-server\server\next-server.js:59:492)
at async Router.execute (D:\Web\StoreApp\nextjs-store\node_modules\next\dist\next-server\server\router.js:25:67)
at async DevServer.run (D:\Web\StoreApp\nextjs-store\node_modules\next\dist\next-server\server\next-server.js:69:1042)
at async DevServer.handleRequest (D:\Web\StoreApp\nextjs-store\node_modules\next\dist\next-server\server\next-server.js:34:504)
I've moved everything in to it's own async function: -
async function setPreferences(itemListed, auctionCheckoutComplete) {
eBay.OAuth2.setCredentials(token.token);
eBay.OAuth2.on("refreshAuthToken", (token) => {
console.log("Refresh token:", token);
updateToken(token);
});
let response = await eBay.trading.SetNotificationPreferences({
UserDeliveryPreferenceArray: {
NotificationEnable: {
EventType: "AuctionCheckoutComplete",
EventEnable: auctionCheckoutComplete,
},
NotificationEnable: {
EventType: "ItemListed",
EventEnable: itemListed,
},
},
});
if (response) {
console.log("Set Pref", response);
}
}
setPreferences().catch((e) => {
consol
e.log("error", { error: e.message });
});
token - return by refreshAuthToken is still undefined
Refresh token: undefined
updateToken Cannot read property 'upsert' of undefined
ah! will refreshAuthToken return null if it didn't need refreshing?
Hm can you update the lib to latest one v3.2.0
.
refreshAuthToken should not be triggered if token is not refreshed...
Please avoid mixing Promise (then) and async/await.
function setPreferences(itemListed, auctionCheckoutComplete) {
eBay.OAuth2.setCredentials(token.token);
eBay.OAuth2.on("refreshAuthToken", (token) => {
console.log("Refresh token:", token);
updateToken(token);
});
return eBay.trading.SetNotificationPreferences({
UserDeliveryPreferenceArray: {
NotificationEnable: {
EventType: "AuctionCheckoutComplete",
EventEnable: auctionCheckoutComplete,
},
NotificationEnable: {
EventType: "ItemListed",
EventEnable: itemListed,
},
},
});
}
setPreferences().then((response) => {
if (response) {
console.log("Set Pref", response);
}
})
.catch((e) => {
console.log("error", { error: e.message });
});
:/ now i'm not even getting the successful response
In my API i'm calling the above setPreferences function: -
case "PUT":
const itemListed = req.body.itemListed;
const auctionCheckoutComplete = req.body.auctionCheckoutComplete;
console.log(
"NOTIFICATION API: Processing PUT - ",
itemListed,
" : ",
auctionCheckoutComplete
);
setPreferences(itemListed, auctionCheckoutComplete);
res.status(200).end();
break;
with the function as you suggested: -
function setPreferences(itemListed, auctionCheckoutComplete) {
eBay.OAuth2.setCredentials(token.token);
eBay.OAuth2.on("refreshAuthToken", (token) => {
console.log("Refresh token:", token);
updateToken(token);
});
return eBay.trading.SetNotificationPreferences({
UserDeliveryPreferenceArray: {
NotificationEnable: {
EventType: "AuctionCheckoutComplete",
EventEnable: auctionCheckoutComplete,
},
NotificationEnable: {
EventType: "ItemListed",
EventEnable: itemListed,
},
},
});
}
setPreferences()
.then((response) => {
if (response) {
console.log("Set Pref", response);
}
})
.catch((e) => {
console.log("error", { error: e.message });
});
I'm no longer getting any console logging from the API or the function .then or any catch errors...
however it IS still trying to refresh the token
Refresh token: undefined
And i'm getting an internal server error from my api request
case "PUT":
const itemListed = req.body.itemListed;
const auctionCheckoutComplete = req.body.auctionCheckoutComplete;
console.log(
"NOTIFICATION API: Processing PUT - ",
itemListed,
" : ",
auctionCheckoutComplete
);
// this is promise. You need to call .then
setPreferences(itemListed, auctionCheckoutComplete).then((response) => {
res.send(response);
}).catch((error) => {
res.sendStatus(500); // or whatever
console.error(error)
})
break;
and remove:
setPreferences()
.then((response) => {
if (response) {
console.log("Set Pref", response);
}
})
.catch((e) => {
console.log("error", { error: e.message });
});
you already calling it in the PUT case.
Mayve you use multiple eBay.OAuth2.on("refreshAuthToken", (token) => {}) ?
I can't explaing why it's logging undefined.. It's not even possible since it's always an object or an error is thrown. Did you updated to latest lib version?
Can you run your App with this env variable:
DEBUG=ebay:* node your-app.js
Not that I'm aware of...
I have a settings page with a button that toggles the preference and triggers sending the SetNotification
<Button
variant="contained"
color="secondary"
onClick={() => togglePreference("itemListed")}
>
<a>{state.itemListed ? "Turn Off" : "Turn On"}</a>
</Button>
function togglePreference(pref) {
var itemListed = state.itemListed === true ? "Enable" : "Disable";
var auctionCheckoutComplete =
state.itemListed === true ? "Enable" : "Enable";
if (pref === "auctionCheckoutComplete") {
auctionCheckoutComplete =
state.auctionCheckoutComplete === true ? "Disable" : "Enable";
} else if (pref === "itemListed") {
itemListed = state.itemListed === true ? "Disable" : "Enable";
}
setPreferences(itemListed, auctionCheckoutComplete)
.then((response) => {
if (response.status === 200) {
console.log("Toggle successful");
} else if (response.status === 500) {
console.log("Toggle unsuccessful");
}
})
.catch((error) => {
console.error(error);
});
}
which calls: -
function setPreferences(itemListed, auctionCheckoutComplete) {
return fetch(`${server}/api/ebay/getPreferences`, {
method: "put",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
auctionCheckoutComplete: auctionCheckoutComplete,
itemListed: itemListed,
}),
});
}
Which then hits the above PUT method of the API: -
case "PUT":
const itemListed = req.body.itemListed;
const auctionCheckoutComplete = req.body.auctionCheckoutComplete;
console.log(
"NOTIFICATION API: Processing PUT - ",
itemListed,
" : ",
auctionCheckoutComplete
);
setPreferences(itemListed, auctionCheckoutComplete)
.then((response) => {
res.send(response);
})
.catch((error) => {
res.sendStatus(500);
console.log(error);
});
break;
I also have a GET method in that API switch that currently calls refreshAuthToken. But for the sake of testing i've commented that all out, so there shouldn't be anything calling it.
With the above I get the 500 from the PUT: -
And to the server console, I can see it's trying the refresh: -
Refresh token: undefined
What do you mean sorry by 'your-app.js' ?
Do you know how you set environment variables in your app?
What error is displayed on the server side?
I've got an env.local file I can put it in, just not sure what 'your-app' would be. Is this the '_app.js'?
Are you calling this
updateToken().catch((e) => {
console.error("updateToken" + e.message);
});
somewhere in your app? Remove it.
As you can see the error is not only from the eBay API itself.
prisma.env_variable
is undefined.
Where do you store the token env_variable
or variable
??
function setPreferences(itemListed, auctionCheckoutComplete) {
const token = await prisma.variable.findFirst({
where: {
variable: "ebayToken",
}
});
eBay.OAuth2.setCredentials(token.token);
eBay.OAuth2.on("refreshAuthToken", (token) => {
const result = await prisma.variable.upsert({
where: {
variable: "ebayToken",
},
update: { token: token },
create: {
variable: "ebayToken",
token: token,
},
});
});
return eBay.trading.SetNotificationPreferences({
UserDeliveryPreferenceArray: {
NotificationEnable: {
EventType: "AuctionCheckoutComplete",
EventEnable: auctionCheckoutComplete,
},
NotificationEnable: {
EventType: "ItemListed",
EventEnable: itemListed,
},
},
});
}
the env_variable was a typo :(
but I can't put the prisma await within the function because it needs to be an async function. Which is why I put the prisma DB update in it's own async function, like that updateToken() and why I was getting the token from the DB outside of my setPreferences function
Ok, this works, when I have a valid token: -
case "PUT":
const itemListed = req.body.itemListed;
const auctionCheckoutComplete = req.body.auctionCheckoutComplete;
console.log(
"NOTIFICATION API: Processing PUT - ",
itemListed,
" : ",
auctionCheckoutComplete
);
setPreferences(itemListed, auctionCheckoutComplete)
.then((response) => {
res.status(200);
console.log(response);
res.send(response);
})
.catch((error) => {
res.status(500);
console.log(error);
});
break;
const token = await prisma.variable.findFirst({
where: { variable: "ebayToken" },
});
async function updateToken(token) {
console.log("Refresh token:", token);
let response = await prisma.variable.upsert({
where: {
variable: "ebayToken",
},
update: { token: token },
create: {
variable: "ebayToken",
token: token,
},
});
if (response) {
console.log("Token Refreshed: ", response);
}
}
function setPreferences(itemListed, auctionCheckoutComplete) {
eBay.OAuth2.setCredentials(token.token);
eBay.OAuth2.on("refreshAuthToken", (token) => {
updateToken(token).catch((e) => {
console.error("Update Token failed: ", e.message);
});
});
return eBay.trading.SetNotificationPreferences({
UserDeliveryPreferenceArray: {
NotificationEnable: {
EventType: "AuctionCheckoutComplete",
EventEnable: auctionCheckoutComplete,
},
NotificationEnable: {
EventType: "ItemListed",
EventEnable: itemListed,
},
},
});
}
It looks good to me. So did work now?
It worked with a valid token. I waited for the token to timeout and reloaded the page. Like before I get the 'invalid scope' error: -
NOTIFICATION API: Processing PUT - Disable : Enable
API resolved without sending a response for /api/ebay/getPreferences, this may result in stalled requests.
EBayInvalidScope: invalid_scope: The requested scope is invalid, unknown, malformed, or exceeds the scope granted to the client
at EBayInvalidScope.EBayError [as constructor] (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\errors\index.js:45:28)
at EBayInvalidScope.EbayApiError [as constructor] (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\errors\index.js:122:24)
at new EBayInvalidScope (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\errors\index.js:182:42)
at Object.exports.handleEBayError (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\errors\index.js:243:15)
at Traditional.<anonymous> (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\api\traditional\index.js:240:34)
at step (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\api\traditional\index.js:76:23)
at Object.throw (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\api\traditional\index.js:57:53)
at rejected (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\api\traditional\index.js:49:65)
at runMicrotasks (<anonymous>)
at processTicksAndRejections (node:internal/process/task_queues:94:5) {
meta: {
message: 'invalid_scope',
description: 'The requested scope is invalid, unknown, malformed, or exceeds the scope granted to the client',
res: { status: 400, statusText: 'Bad Request', headers: [Object] },
req: {
url: 'https://api.sandbox.ebay.com/identity/v1/oauth2/token',
method: 'post',
headers: [Object],
params: undefined
},
[Symbol(raw-error)]: Error: Request failed with status code 400
at createError (D:\Web\StoreApp\nextjs-store\node_modules\axios\lib\core\createError.js:16:15)
at settle (D:\Web\StoreApp\nextjs-store\node_modules\axios\lib\core\settle.js:17:12)
at IncomingMessage.handleStreamEnd (D:\Web\StoreApp\nextjs-store\node_modules\axios\lib\adapters\http.js:260:11)
at IncomingMessage.emit (node:events:391:22)
at endReadableNT (node:internal/streams/readable:1307:12)
at processTicksAndRejections (node:internal/process/task_queues:81:21) {
config: [Object],
request: [ClientRequest],
response: [Object],
isAxiosError: true,
toJSON: [Function: toJSON]
}
}
}
So I'm assuming it doesn't refresh the token. It definitely isn't calling updateToken
I need to check out that API error: -
API resolved without sending a response for /api/ebay/getPreferences, this may result in stalled requests.
Would it be possible to share your whole git repo so I can reproduce it?
Yeah of course, it's all in github, i'll invite you
I need to update the GET case with the correct promise from the PUT we've fixed. But that's not what i'm logging the error from.
After the merge...I'm afraid :(
NOTIFICATION API: Processing PUT - Disable : Enable
API resolved without sending a response for /api/ebay/getPreferences, this may result in stalled requests.
EBayInvalidScope: invalid_scope: The requested scope is invalid, unknown, malformed, or exceeds the scope granted to the client
at EBayInvalidScope.EBayError [as constructor] (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\errors\index.js:45:28)
at EBayInvalidScope.EbayApiError [as constructor] (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\errors\index.js:122:24)
at new EBayInvalidScope (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\errors\index.js:182:42)
at Object.exports.handleEBayError (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\errors\index.js:243:15)
at Traditional.<anonymous> (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\api\traditional\index.js:240:34)
at step (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\api\traditional\index.js:76:23)
at Object.throw (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\api\traditional\index.js:57:53)
at rejected (D:\Web\StoreApp\nextjs-store\node_modules\@hendt\ebay-api\lib\api\traditional\index.js:49:65)
at runMicrotasks (<anonymous>)
at processTicksAndRejections (node:internal/process/task_queues:94:5) {
meta: {
message: 'invalid_scope',
description: 'The requested scope is invalid, unknown, malformed, or exceeds the scope granted to the client',
res: { status: 400, statusText: 'Bad Request', headers: [Object] },
req: {
url: 'https://api.sandbox.ebay.com/identity/v1/oauth2/token',
method: 'post',
headers: [Object],
params: undefined
},
[Symbol(raw-error)]: Error: Request failed with status code 400
at createError (D:\Web\StoreApp\nextjs-store\node_modules\axios\lib\core\createError.js:16:15)
at settle (D:\Web\StoreApp\nextjs-store\node_modules\axios\lib\core\settle.js:17:12)
at IncomingMessage.handleStreamEnd (D:\Web\StoreApp\nextjs-store\node_modules\axios\lib\adapters\http.js:260:11)
at IncomingMessage.emit (node:events:391:22)
at endReadableNT (node:internal/streams/readable:1307:12)
at processTicksAndRejections (node:internal/process/task_queues:81:21) {
config: [Object],
request: [ClientRequest],
response: [Object],
isAxiosError: true,
toJSON: [Function: toJSON]
}
}
}
I think I know the problem :) you are missing the trading scopes. You use it in getPreference but not in the success.js and it's requuered where you generate the ebay auth url. Keep all scopes identically, delete the token from db and do the auth flow again.
aaahhh damn XD
Well, I've got this scope everywhere now. Do you know where I can find a list of valid scopes? I'm getting invalid scope response for these
scope: [ "https://api.ebay.com/oauth/api_scope", "https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly", "https://api.ebay.com/oauth/api_scope/sell.fulfillment", "https://api.ebay.com/oauth/api_scope/trading.readonly", "https://api.ebay.com/oauth/api_scope/trading", ],
I can only find this https://developer.ebay.com/api-docs/static/oauth-scopes.html#scopes-and-flows Which explains how to use scopes, but not a definition of each one
Well, these are the scopes what I'm using. But it would great to have it in the docs...
scope: [
'https://api.ebay.com/oauth/api_scope',
'https://api.ebay.com/oauth/api_scope/sell.marketing.readonly',
'https://api.ebay.com/oauth/api_scope/sell.marketing',
'https://api.ebay.com/oauth/api_scope/sell.inventory.readonly',
'https://api.ebay.com/oauth/api_scope/sell.inventory',
'https://api.ebay.com/oauth/api_scope/sell.account.readonly',
'https://api.ebay.com/oauth/api_scope/sell.account',
'https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly',
'https://api.ebay.com/oauth/api_scope/sell.fulfillment',
'https://api.ebay.com/oauth/api_scope/sell.analytics.readonly',
'https://api.ebay.com/oauth/api_scope/sell.finances',
'https://api.ebay.com/oauth/api_scope/sell.payment.dispute',
'https://api.ebay.com/oauth/api_scope/commerce.identity.readonly'
]
Found this: - https://developer.ebay.com/api-docs/static/oauth-trad-apis.html#OAuth
So I tried using these based on their recommendation of what to use
However, I was getting invalid scope
error still.
Tried your list, and it works :D
So, all working again....will wait for the token to timeout again, and see if the refresh works!
It worked!!! Thank you so much @dantio for all your help
Hi, I'm having problems with permissions and suspect I'm doing something stupid. I don't fully understand the eBay auth tokens.
This is what I'm using:
I'm currently getting the 'Insufficient permissions to fulfill the request.' error.
I can successfully get an Auth Token from using ebay-oauth-nodejs-client
Can I use this token in your API call?
Thanks in advance for the help!