Adyen Drop-in - how to pass unique order ID? - javascript

I have been trying to use the Adyen Drop-in component to make payments on the Razor pages site I am developing. I have got a test version running that makes a payment for a hard-coded amount but I have yet to figure out how to pass a unique order ID to my API endpoint making the payment request.
Taking the examples from https://docs.adyen.com/online-payments/drop-in-web, the drop-in component is mounted via JavaScript using
const checkout = new AdyenCheckout(configuration);
const dropin = checkout.create('dropin').mount('#dropin-container');
where the configuration object is created with something like
const configuration = {
paymentMethodsResponse: paymentMethodsResponse, // The `/paymentMethods` response from the server.
clientKey: "YOUR_CLIENT_KEY", // Web Drop-in versions before 3.10.1 use originKey instead of clientKey.
locale: "en-US",
environment: "test",
onSubmit: (state, dropin) => {
// Your function calling your server to make the `/payments` request
makePayment(state.data)
.then(response => {
if (response.action) {
// Drop-in handles the action object from the /payments response
dropin.handleAction(response.action);
} else {
// Your function to show the final result to the shopper
showFinalResult(response);
}
})
.catch(error => {
throw Error(error);
});
},
onAdditionalDetails: (state, dropin) => {
// Your function calling your server to make a `/payments/details` request
makeDetailsCall(state.data)
.then(response => {
if (response.action) {
// Drop-in handles the action object from the /payments response
dropin.handleAction(response.action);
} else {
// Your function to show the final result to the shopper
showFinalResult(response);
}
})
.catch(error => {
throw Error(error);
});
}
};
Adyen's own JavaScript then supplies the state object for the onSubmit method, so that my API endpoint gets called with a PaymentRequest object created (somehow) from the state.data.
However, without being able to get a unique order ID into this PaymentRequest object, my server-side code does not know what amount to set. Note that one can set an Amount object in the configuration object but this is just used to display the value on the Drop-in component - the value is not passed to the server.
So how does one pass a unique order ID via the Drop-in component?

The Adyen docs don't explicitly provide an example here, but the makePayment() and makeDetailsCall() presume that you will take the state.data and post back to your server. You need to implement your own code here. At that point, you could add additional information like any identifiers.
Here is an example implementation as a reference:
async function makePayment(state_data) {
const order_id = ""; // You need to provide this however your client stores it.
const json_data = {
order_id,
state_data,
};
const res = await fetch("[url to your server's endpoint]", {
method: "POST",
body: JSON.stringify(json_data),
headers: {
"Content-Type": "application/json",
},
});
return await res.json();
}
Another helpful resource could be the Adyen node.js/express tutorial. It is more explicit on implementation details so might help remove some ambiguity.

Related

onError Authentication with refresh token

In the Apollographql documentation it states:
The onError link can retry a failed operation based on the type of GraphQL error that's returned. For example, when using token-based authentication, you might want to automatically handle re-authentication when the token expires.
This is followed up by their sample code:
onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
switch (err.extensions.code) {
// Apollo Server sets code to UNAUTHENTICATED
// when an AuthenticationError is thrown in a resolver
case "UNAUTHENTICATED":
// Modify the operation context with a new token
const oldHeaders = operation.getContext().headers;
operation.setContext({
headers: {
...oldHeaders,
authorization: getNewToken(),
},
});
// Retry the request, returning the new observable
return forward(operation);
}
}
}
// To retry on network errors, we recommend the RetryLink
// instead of the onError link. This just logs the error.
if (networkError) {
console.log(`[Network error]: ${networkError}`);
}
});
My question is in regards to the getNewToken(), as no code was provided for this function, I want to know (assuming this is another request to the backend and I am not sure how it could not be), if you are able to and or supposed to use query/mutation in graphql or make the request through axios for example.
One problem, if it can/should be a graphql query or mutation, is to get the new token, the onError code is defined in the same file as the ApolloClient as ApolloClient needs access to onError, thus when trying to implement this as retrieving a new token through a graphql mutation I got the following error:
React Hook "useApolloClient" is called in function "refresh" that is
neither a React function component nor a custom React Hook function.
After trying to useQuery/useMutation hook and realizing I cannot outside of a react component and at the top level I found this post whose answers suggested you can use useApolloClient.mutate instead but I still ran into issues. My code was (and tried multiple iterations of this same code like useApolloClient() outside of the function and inside etc.):
const refresh = () => {
const client = useApolloClient();
const refreshFunc = () => {
client
.mutate({ mutation: GET_NEW_TOKEN })
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
};
refreshFunc();
};
I could capitalize Refresh but this still would not work and would break the rules of hooks.
And to clarify all the above would do is I would replace the console.logs with setting session storage to the retrieved new token and then re trying the original request with onError.
Now in another post I found when looking into this, the users getNewToken request was a rest request using axios:
const getNewToken = async () => {
try {
const { data } = await axios.post(
"https://xxx/api/v2/refresh",
{ token: localStorage.getItem("refreshToken") }
);
localStorage.setItem("refreshToken", data.refresh_token);
return data.access_token;
} catch (error) {
console.log(error);
}
};
Now from my understanding, if I wanted to implement it this way I would have to change my backend to include express as I am only using apolloserver. Now I could definitely be wrong about that as my backend knowledge is quite limited and would love to be corrected their.
So my question is, what is the best way to do this, whether natively using graphql queries/mutations (if possible), doing it with axios, or maybe their is another best practice for this seemingly common task I am unaware of.

What's the proper way to use RTK Query when dealing with multiple base URLS?

I have migrated towards RTK and enjoying it immensely so far, but one thing I got stuck on is the following situation:
We have (for keeping it simple's sake) two endpoints:
www.domain-customer.com <- fetching customer data
www.domain-order.com <- can mutate some user data here
And the website itself is hosted on another domain.
I need to fetch data from the customer endpoint, but in order to update certain things I need to make a mutation to the order endpoint. At first I thought I should define a createApi per base URL, but then I'm pretty sure I can't have the invalidation. I would like to have this previous mutation invalidate the data for the customer, so that the new data gets refetched.
So this is what I have come up with, but I'd like some input on if this is the way to move forward.
export const customerApi = createApi({
reducerPath: "/../",
baseQuery: fetchBaseQuery({ baseUrl: "https://www.domain-customer.com/" }),
endpoints: (builder) => ({
// write provides tag stuff here
getCustomerExample: builder.query({ query: (name) => `customer/${name}` }),
// please ignore the details of the mutation, I haven't practiced much with it yet.
updateCustomer: builder.mutation({
queryFn: async (name) => {
const response = await fetch(
`https://www.domain-order.com/updateCustomer`,
{update mutation stuff here}
);
const data = await response.json();
return { data };
}
// write invalidate stuff here
})
})
});
Is this the way to go about it? Or should there even be a giant createAPI that will hold all the mutations and queries?
Generally, yes, you should have one single createApi if data is connected enough that you want to invalidate them.
Note that while most examples just show queries to something under baseQuery, you can also just have a url parameter (or string) returned from query that contains a full domain -fetchBaseQuery 100% supports that use case.
So in your case:
updateCustomer: builder.mutation({
query: (name) => ({
url: `https://www.domain-order.com/updateCustomer`,
// update mutation stuff here
})
// write invalidate stuff here
})

GraphQL/Apollo Client: get data from cache if a query is a subset of a previous query

I'm new to GraphQL and I thought it would do this automatically.
E.g. if I fetch a list of posts, then open an individual post, the data for that post is already in memory. I expected GraphQL to be able to re-use the data from cache, but it makes another network request.
The schema is something like:
type Query {
posts: [Post]
post(id: ID): Post
}
I'm using React. In PostsRoute, I have:
useQuery(gql`
query {
posts {
id
title
}
}
`);
In PostRoute, I have:
useQuery(gql`
query {
post(id: ${postId}) {
id
title
}
}
`);
Is there something I need to configure for Apollo to use the cached data automatically? Or is this something that Apollo doesn't support by default?
Apollo 2
If you are using Apollo 2, what you're looking for is probably cacheRedirects. It is used to intercept a query and resolve it using the cache. The linked document actually explains exactly your use-case so I recommend you look at it, but for posterity, here's how I do it personally (adapted to your situation).
In your InMemoryCache options when you instantiate it, add a cacheRedirects field and specify a custom resolver for your post query, like so:
const cache = new InMemoryCache({
cacheRedirects: {
Query: {
post: (_, {id}, {getCacheKey}) => getCacheKey({__typename: "Post", id}),
},
},
});
This assumes that the __typename of your post is Post. First argument of the custom resolver is the result of the ROOT_QUERY, which we don't use here. Second argument is the arguments that are passed to the query ({id: "some_id"} here) and the third one is the context, containing client, cache and getCacheKey. Only getCacheKey is useful to us here. That method take a __typename and an id, and returns its key in the cache.
This completes the custom resolver. Now when you query post(id: some_id) it will look at the custom resolver, receive the cache key and return the result from the cache matching that key.
Apollo 3
cacheRedirects has been removed in Apollo 3, but the same functionality can be achieved with field policies. You still need to set it up when instantiating InMemoryCache, but it's slightly different:
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
post: (_, {args, toReference}) => toReference({__typename: "Post", id: args.id}),
},
},
},
});
We define a field policy read on the field post on type Query. Note that the second argument now includes both args and the helper utilities. This includes toReference, that's basically the new getCacheKey.

Updating populated array in MongoDB - patch request is being accepted but not updating with new item

I am trying to update a populated array stored in a MongoDB database with a new item using Javascript & Node.js, and while the patch request is technically successful, it does not update with the ID of the new item, and checking the console, only the old IDs are passed through to the DB, not the newly created one. To be more specific, I am building out an "Add new question" feature for a quiz application.
I tried to utilize .depopulate(), thinking it was something related to .populate() that was causing the issue but received an error stating that it was not a function, and am now thinking that is not the issue. Is there something I am overlooking with my code? Various snippets are included below.
Route (which works in other functions that update other aspects of the quiz):
router.patch('/quizzes/:id', requireToken, removeBlanks, (req, res, next) => {
delete req.body.quiz.owner
Quiz.findById(req.params.id)
.then(handle404)
.then(quiz => {
requireOwnership(req, quiz)
return quiz.updateOne(req.body.quiz)
})
.then(() => res.sendStatus(204))
.catch(next)
})
How questions are stored in the quiz Mongoose model:
questions: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Question'
}]
Here, I am pulling out the existing objectIds, as my 'questions' Mongoose schema is type: mongoose.Schema.Types.ObjectId,
const reAddQuestionIds = () => {
const quiz = store.quizData.questions
quiz.forEach(function (arrayItem) {
store.questions.push(arrayItem._id)
})
}
Event function to create question & add the newly created question to the quiz:
const onAddQuestion = event => {
event.preventDefault()
const form = event.target
const formData = getFormFields(form)
// call above function
reAddQuestionIds()
api.addQuestion(formData)
// get the newly created question
.then(res => api.getOneQuestion(res.question._id)
// push the id of the new question to the array of existing questions
// this line seems to be the issue?
.then(res => store.questions.push(res.question._id)))
.then(store.quizData.numOfQuestions++)
// call ajax function to update api
.then(api.addQuestionToQuiz()
.then(console.log))
.catch(console.error)
}
API call:
const addQuestionToQuiz = () => {
console.log('store.questions: ', store.questions)
return $.ajax({
url: config.apiUrl + '/quizzes/' + store.quizData._id,
method: 'PATCH',
headers: {
Authorization: `Token token=${store.user.token}`
},
data: {
quiz: {
questions: store.questions,
numOfQuestions: store.quizData.numOfQuestions
}
}
})
}
Now, a unexpected thing that happens, is in the above console log: when I check what is currently in the stored array in Chrome devtools, it shows there being one less object than what should be in there (i.e. if there were two questions previously, then I added the new question, it still just shows two objects), but when I expand, the new ID is there and it shows it as the correct length. And again, in checking what is passed through to the backend route, only the previously-created IDs are passed through.
Update: error was in the API call to the event handler function. I removed adding the code incrementing the number of questions (moved this to the API call), and in my third .then(), made sure to take the response and pass it to the api.AddQuestionToQuiz() call.
Discovered by logging store.questions upon successful creation of the new question, and saw that all questions were there, whereas the console.log in the API call function was missing the newly created question.
api.addQuestion(formData)
.then(res => api.getOneQuestion(res.question._id)
.then(res => store.questions.push(res.question._id)))
.then(res => api.addQuestionToQuiz()
.then(console.log))
.catch(console.error)

React/Redux chaining a second API request and dispatch

Still getting used to this.
I have checked out questions here and here and I still feel uncomfortable about how to go about this. These are not clear from my perspective.
Basically I have clicked on the "Login" button and have requested a JWT login token and succesfully received it. What I have noticed is that I can at this point download those rarely changed lists like states or suburbs/postcodes which are large (the latter) and should be at the spa's immediate use rather than tacking it onto a call for a client.
I want to, upon successfully receiving the token immediately do an API call for those lists and store them in local storage. There is no "event" it should fire straight after a successful retreival of a JWT...
Here is the action for retreiving a JWT in react:
export const requestLoginToken = (username, password) =>
(dispatch, getState) => {
dispatch({type: REQUEST_LOGIN_TOKEN, payload: username})
const payload = {
userName: username,
password: password,
}
const task = fetch('/api/jwt', {
method: 'POST',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
})
.then(handleErrors)
.then(response => response.json())
.then(data => {
dispatch({type: RECEIVE_LOGIN_TOKEN, payload: data})
saveJwt(data)
})
.catch(error => {
clearJwt()
dispatch({type: ERROR_LOGIN_TOKEN, payload: error.message})
})
addTask(task)
return task
}
I beleive the place for adding a second API call would be after "saveJwt()" but how.
Do/can I send it off to another export const action/function in another part of the application?
If I write something similar to this and by putting the name of the function in with a parameter of "JWT" eg
.then(retrieveSelectData)
that it will go off to that separate folder with that export function and execute an API call at the same time applying a dispatch...and then return..
Could some one outline if this is a correct and reasonable way of making two API calls as one. I still have to get the JWT (and use it in the second) so I cant do the second call without the first.
If i understand your goal correctly, what you need here is a middleWare that will catch all actions that dispatched before the reducers catches them and can accept functions and holds a ref to the dispatcher.
Note that reducers can only accept actions that are plain objects and can't accept functions.
Enters redux-thunk, a middleware that does just that (and more) in only 11 lines of code.
It catches all actions (before the reducers) and checks to see if this action === 'function'.
If it is a function then it will call that function with dispatch as the argument.
If it's not a function it will leave it alone and let the reducers do their job.
Something like this:
function loadSomeThings() {
return dispatch => {
fetchFirstThingAsync.then(data => { // first API call
dispatch({ type: 'FIRST_THING_SUCESS', data }); // you can dispatch this action if you want to let reducers take care of the first API call
return fetchSecondThingAsync(data), // another API call with the data received from the first call that returns a promise
})
.then(data => {
dispatch({ type: 'SECOND_THING_SUCESS', data }); // the reducers will handle this one as its the object they are waiting for
});
};
}

Categories

Resources