How would you normally implement react paypal with a backend - reactjs

So this is how I've been told to use paypal react integration on my e-commerce site although I'm not sure if it's the best (I'll tell you later why)
useEffect(() =>{
window.paypal.Buttons({
createOrder: (data, actions, err) => {
return actions.order.create({
intent: "CAPTURE",
purchase_units: [
{
description: 'book',
amount:{
currency_code: 'USD',
value: 100
}
}
]
})
},
onApprove: async (data, action) =>{
completeOrder()
},
onError: err => console.log(err.message)
}).render(buttonsDiv.current)
// console.log(window.paypal)
}, [])
the purpose completeOrder() function is to send a request to the backend in order to verify the order before putting a completed label in the the order
so the problem I'm having with this is completeOrder() runs after the payment was completed and in my experience things like server down or maintenance might occur when the user is paying and that can lead to the user paying for nothing because the order isn't completed
the first solution that I'm looking for is to cancel the payment based on the response from the server. So if the response from the server is an error it will cancel the previous payment. But due to my lack of knowledge is don't see how that's possible

The normal solution is to use the official react-paypal-js for the frontend order approval, and the Checkout-NodeJS-SDK for order creation and capture via API. Server logic to mark an order as completed should be done in the capture route, before returning the capture result JSON to the front-end approval flow.
For a demo of functions that call a server from approval JS, see the server demo. You can implement such client-side functions (createOrder and onApprove) from your react code, and have them call the 2 server routes you need to create.

Related

How to get Stripe payment intent ID for updating payment intent for PaymentElement

Is there a canonical way to get the PaymentIntent ID in order to update a PaymentIntent? (i.e., to maintain security, etc...).
Specifically, all of the documentation and examples I can find of using Payment Element have you set up a payment intent early and only return a client_secret to the client.
export default async function createPaymentIntentHandler(req, res) {
const stripe = new Stripe(STRIPE_SECRET_KEY));
const body = JSON.parse(req.body);
const paymentIntent = await stripe.paymentIntents.create({
currency: 'USD',
amount: 100,
automatic_payment_methods: {
enabled: true,
},
});
res.status(200).send({clientSecret: paymentIntent.client_secret})
}
In order to update that (e.g., if the user changes the quantity on an order) you need the PaymentIntent ID. Now, the PaymentIntent created on the backend already has it, so you could just return it at the same time as the clientSecret:
res.status(200).send({
clientSecret: paymentIntent.client_secret,
pi_ID: paymentIntent.id
})
and have the client send that when hitting the update payment intent API. I guess the main question is, is there any reason not to do that?
Because there seem to be at least two other ways. First, although this seems like it's probably a bad idea, one could just parse it on the server from the client secret. That is, the client secret takes the form <payment_intent_id>_<secret>, so you could just continue sending only the client secret back to the client on the create request, and then extract the id when the client calls the update api with their client secret.
Second, there exists a client-side Stripe API for querying a payment intent. So, when the client wants to update the payment intent, it could
stripe
.retrievePaymentIntent('{PAYMENT_INTENT_CLIENT_SECRET}')
.then(function(result) {
// call update API
});
This latter seems like unnecessary overhead compared to just sending the ID back as part of the original create request, but maybe there's some reason this is actually preferred?

Detaching user data snapshot listeners on web app

I have a snapshot listener that listen for real time changes on user data I'm storing with Firestore. The listener is there due to a chat feature I am developing because I need to update the list of chat Id's that the user has in realtime in case a new chat was added. The problem is with detaching the listener when the user is disconnected or has lost connection. Any suggestions on how I could achieve this without using the onDisconnect method from Real Time DB since I can't call the detachListner() method with that.
Example:
here is my user doc
{
name:"Brad"
avatar:"https://avatar.com"
chats: [
{
id: 1.,
member_name:"John",
avatar:""
},
{
id: 2.,
member_name:"John",
avatar:""
},
]
}
I have an active snapshot listener on this doc,
let unsubscribeListener;
export const getChatSnapshot = (user_id, callback) => {
unsubscribeChatListener = onSnapshot(
doc(db, "users", user_id),
(doc) => {
callback(doc.data());
},
(error) => {
console.log(error.message);
}
);
};
is there a way disconnect the listener when user disconnect or close the page.
since with onDisconnect I only have set, delete ...
Is there a way to have something like this.
onDisconnect(userStatusDatabaseRef)
// call unsubscribeListener when user is disconnected.
// like unsubscribeListener()
.set(isOfflineForDatabase)
.then(() => {
set(userStatusDatabaseRef, isOnlineForDatabase);
});
If you're asking whether you can perform a write to Firestore after the user disconnections, similar to Realtime Database's onDisconnect handlers, the answer is unfortunately no.
Firestore uses a different protocol than Realtime Database, and that protocol doesn't allow for detecting the first connection or disconnecting. If you need those features within Firebase, Realtime Database is your only option.
Keep in mind that you can use both Firestore and Realtime Database in the same application. In fact, there's a solution guide that explains how to build a presence system on Firestore using Realtime Database and Cloud Functions behind the scenes.

react-paypal-button-v2 returning the wrong order id

I was trying to debug a problem related to refunding Paypal orders (in a sandbox environment) using order IDs (which were stored previously). Every time I tried to perform a refund, the Paypal API would return an INVALID_RESOURCE_ID error, meaning that no such order existed. After much debugging, I have made a revelation with the initial process when I stored said order ID. The following method is how I am retrieving and storing said order id:
const onApprove = (data, actions) => {
// Redux method of saving checkout in backend with order ID via using data.orderID
dispatch(saveCheckout(data.orderID);
return actions.order.capture();
}
<PayPalButton
amount={totalPrice}
currency= "AUD"
createOrder={(data, actions) => createOrder(data, actions)}
onApprove={(data, actions) => onApprove(data, actions)}
options={{
clientId: "<placeholder>",
currency: "AUD"
}}
/>
I am using the recommended data.orderID from the docs and yet, upon inspecting the network tab, the following is shown:
{"id":"5RJ421191B663801G","intent":"CAPTURE","status":"COMPLETED","purchase_units":[{"reference_id":"default","amount":{"currency_code":"AUD","value":"24.00"},"payee":{"email_address":"sb-sg4zd7438633#business.example.com","merchant_id":"EJ7NSJGC6SRXQ"},"shipping":{"name":{"full_name":"John Doe"},"address":{"address_line_1":"1 Cheeseman Ave Brighton East","admin_area_2":"Melbourne","admin_area_1":"Victoria","postal_code":"3001","country_code":"AU"}},"payments":{"captures":[{"id":"7A2856455D561633D","status":"COMPLETED","amount":{"currency_code":"AUD","value":"24.00"},"final_capture":true,"seller_protection":{"status":"ELIGIBLE","dispute_categories":["ITEM_NOT_RECEIVED","UNAUTHORIZED_TRANSACTION"]},"create_time":"2021-10-11T00:40:58Z","update_time":"2021-10-11T00:40:58Z"}]}}],"payer":{"name":{"given_name":"John","surname":"Doe"},"email_address":"sb-432azn7439880#personal.example.com","payer_id":"KMEQSKCLCLUZ4","address":{"country_code":"AU"}},"create_time":"2021-10-11T00:40:48Z","update_time":"2021-10-11T00:40:58Z","links":[{"href":"https://api.sandbox.paypal.com/v2/checkout/orders/5RJ421191B663801G","rel":"self","method":"GET"}]}
The id saved by onApprove is 5RJ421191B663801G but there is another ID under captures and id which is 7A2856455D561633D. This is the actual order id I need to save in order to make the refund later on. However, I am struggling as to how I can retrieve this value as that id value seems to be only visible via the network. The objects returned via the onApprove and action.order.get() methods only return the first "false" id. Any advice would be greatly appreciated.
These are two separate types of IDs, the order ID (used only during buyer checkout approval), and the payment/transaction ID (which only exists after an order is captured, and is the one needed for any later refund or accounting purposes)
Since you are capturing on the client side with actions.order.capture(), this is where you would need to add a .then(function(data){ ... }) to do something with the capture data (particularly data.purchase_units[0].payments.captures[0].id). That is the id you would use for a refund.
In actual best practice, if anything important needs to be done with the capture id -- such as storing it in a database for reference -- you should not be creating and capturing orders on the client side, and instead calling a server-side integration where that database write will be performed.
Follow the Set up standard payments guide and make 2 routes on your server, one for 'Create Order' and one for 'Capture Order', documented here. Both routes should return only JSON data (no HTML or text). Inside the 2nd route, when the capture API is successful you should store its resulting payment details in your database (particularly the aforementioned purchase_units[0].payments.captures[0].id, which is the PayPal transaction ID) and perform any necessary business logic (such as sending confirmation emails or reserving product) immediately before forwarding your return JSON to the frontend caller.
Pair those 2 routes with the frontend approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server
Or for react, use the official react-paypal-js

stripe paymentRequest with total amount 0$

when I'm trying to make payment with payment request button I need to set 0$ amount when creating payment intent for it, but I can't, cause for creating payment intent stripe requires min 0.50$. I need it cause I'm using it for metered subscription type and don't have to charge client when subscribing. I found solution to refund after successfully subscription, but I don't like it.
I'm creating payment_intent with this api
app.post('/api/client-secret', async (req, res) => {
try {
const { currency, amount } = req.body;
console.log(req.body)
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
payment_method_types: ['card'],
});
res.json({ clientSecret: paymentIntent.client_secret });
} catch (error) {
console.log("error1", error)
}
});
maybe there is other way or method to create client_secret for stripe.paymentRequest()
The payment_intent object has a field called capture_method. You can set this manual, in which case the payment is not immediately captured by stripe. Pay attention to the parenthesis:
(Not all payment methods support this)
On a different note, if this is a subscription, then why not use the subscription api or even better, sessions. I suggest sessions because it handles all sorts of issues that may arise during a transaction, and all you need to do is wait for the session to complete, and everything will be taken care of.
Also note that the price object (which represents the items you are selling) comes with a field called usage_type which allows you specify that the item is metered, so stripe can handle the billing for you if someone purchases that item.
The stripe api is vast and well documented, so I hope this answer helps you discover a solution quickly.
If you're not charging the user upfront, you should use SetupIntents to save and attach card details to the Customer for charging them in the future — when they’re offline : https://stripe.com/docs/payments/save-and-reuse

Firebase - Best Practice For Server Firestore Reads For Server-Side Rendering

I have a server-side-rendered reactjs app using firebase firestore.
I have an area of my site that server-side-renders content that needs to be retrieved from firestore.
Currently, I am using firestore rules to allow anyone to read data from these particular docs
What worries me is that some bad person could setup a script to just continuously hit my database with reads and rack up my bills (since we are charged on a per-read basis, it seems that it's never wise to allow anyone to perform reads.)
Current Rule
// Allow anonymous users to read feeds
match /landingPageFeeds/{pageId}/feeds/newsFeed {
allow read: if true;
}
Best Way Forward?
How do I allow my server-side script to read from firestore, but not allow anyone else to do so?
Keep in mind, this is an initial action that runs server-side before hydrating the client-side with the pre-loaded state. This function / action is also shared with client-side for page-to-page navigation.
I considered anonymous login - which worked, however, this generated a new anonymous user with every page load - and Firebase does throttle new email/password and anonymous user accounts. It did not seem practical.
Solution
Per Doug's comment, I thought about the admin SDK more. I ended up creating a separate API in firebase functions for anonymous requests requiring secure firestore reads that can be cached.
Goals
Continue to deny public reads of my firestore database
Allow anonymous users to trigger firestore reads for server-side-rendered reactjs pages that require data from Firestore database (like first-time visitors, search engines).
Prevent "read spam" where a third party could hit my database with millions of reads to drive up my cloud costs by using server-side CDN cache for the responses. (by invoking unnessary reads in a loop, I once racked up a huge bill on accident - I want to make sure strangers can't do this maliciously)
Admin SDK & Firebase Function Caching
The admin SDK allows me to securely read from firestore. My firestore security rules can deny access to non-authenticated users.
Firebase functions that are handling GET requests support server caching the response. This means that subsequent hits from identical queries will not re-run all of my functions (firebase reads, other function invocations) - it will just instantly respond with the same data again.
Process
Anonymous client visits a server-side rendered reactjs page
Initial load rendering on server triggers a firebase function (https trigger)
Firebase function uses Admin SDK to read from secured firestore database
Function caches the response for 3 hours res.set('Cache-Control', 'public, max-age=600, s-maxage=10800');
Subsequent requests from any client anywhere for the next 3 hours are served from the cache - avoiding unnecessary reads or additional computation / resource usage
Note - caching does not work on local - must deploy to firebase to test caching effect.
Example Function
const functions = require("firebase-functions");
const cors = require('cors')({origin: true});
const { sendResponse } = require("./includes/sendResponse");
const { getFirestoreDataWithAdminSDK } = require("./includes/getFirestoreDataWithAdminSDK");
const cachedApi = functions.https.onRequest((req, res) => {
cors(req, res, async () => {
// Set a cache for the response to limit the impact of identical request on expensive resources
res.set('Cache-Control', 'public, max-age=600, s-maxage=10800');
// If POST - response with bad request code - POST requests are not cached
if(req.method === "POST") {
return sendResponse(res, 400);
} else {
// Get GET request action from query
let action = (req.query.action) ? req.query.action : null;
console.log("Action: ", action);
try {
// Handle Actions Appropriately
switch(true) {
// Get Feed Data
case(action === "feed"): {
console.log("Getting feed...");
// Get feed id
let feedId = (req.query.feedId) ? req.query.feedId : null;
// Get feed data
let feedData = await getFirestoreDataWithAdminSDK(feedId);
return sendResponse(res, 200, feedData);
}
// No valid action specified
default: {
return sendResponse(res, 400);
}
}
} catch(err) {
console.log("Cached API Error: ", err);
return sendResponse(res, 500);
}
}
});
});
module.exports = {
cachedApi
}

Resources