Creating and Capturing payment with paypal v2 node.js - node.js

I am trying to integrate PayPal server-side payment on a website with a new v2 since v1 is deprecated. Since I had to include quite a lot of code from their git, i will post here everything for a working example
In v1 i could do it really easily:
app.get('/create', function (req, res) {
//build PayPal payment request
let payReq = JSON.stringify({
'intent': 'sale',
'redirect_urls': {
'return_url': 'http://localhost:8081/process',
'cancel_url': 'http://localhost:8081/cancel'
},
'payer': {
'payment_method': 'paypal'
},
'transactions': [{
'amount': {
'total': '7.47',
'currency': 'USD'
},
'description': 'This is the payment transaction description.'
}]
});
paypal.payment.create(payReq, function (error, payment) {
if (error) {
console.error(error);
} else {
//capture HATEOAS links
let links = {};
payment.links.forEach(function (linkObj) {
links[linkObj.rel] = {
'href': linkObj.href,
'method': linkObj.method
};
})
//if redirect url present, redirect user
if (links.hasOwnProperty('approval_url')) {
res.redirect(links['approval_url'].href);
} else {
console.error('no redirect URI present');
}
}
});
});
app.get('/process', function (req, res) {
let paymentId = req.query.paymentId;
let payerId = {'payer_id': req.query.PayerID};
paypal.payment.execute(paymentId, payerId, function (error, payment) {
if (error) {
console.error(error);
} else {
if (payment.state == 'approved') {
const payerCountry = payment.payer.payer_info.country_code;
const total = payment.transactions[0].amount.total;
const currency = payment.transactions[0].amount.currency;
savePayment(payerCountry, total, currency);
res.send('payment completed successfully ' + cnt++);
} else {
res.send('payment not successful');
}
}
});
});
The create endpoint basically creates the order, the paypal API returns hateos links and the controller says the browser should redirect to that link. Once redirected , if user approves payment on paypal site he is redirected to on of
'redirect_urls': {
'return_url': 'http://localhost:8081/process',
'cancel_url': 'http://localhost:8081/cancel'
},
process endpoints retrieves PAYMENT ID and PAYER ID from query and captures the order - allowing me to do whatever i want to do ( e.g save payment in db ) in callback.
Now v2 seems like huge mess:
Following devs guide i have created
Paypal button (copy paste ):
import React, {useEffect} from "react";
import {PayPalButtons, PayPalScriptProvider, usePayPalScriptReducer} from "#paypal/react-paypal-js";
// This values are the props in the UI
const amount = "2";
const currency = "USD";
const style = {"layout": "vertical"};
const ButtonWrapper = ({currency, showSpinner}) => {
// usePayPalScriptReducer can be use only inside children of PayPalScriptProviders
// This is the main reason to wrap the PayPalButtons in a new component
const [{options, isPending}, dispatch] = usePayPalScriptReducer();
useEffect(() => {
dispatch({
type: "resetOptions",
value: {
...options,
currency: currency,
},
});
}, [currency, showSpinner]);
const createOrder = (data, actions) => {
console.log("create")
return fetch('http://localhost:8081/create', {
method: 'post'
}).then(function (res) {
return res.json();
}).then(function (orderData) {
console.log(orderData);
window.location = orderData.redirect;
});
}
// Call your server to finalize the transaction
const onApprove = (data, actions) => {
console.log("approve")
return fetch('/process', {
method: 'post'
}).then(function (res) {
return res.json();
}).then(function (orderData) {
// Three cases to handle:
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
// (2) Other non-recoverable errors -> Show a failure message
// (3) Successful transaction -> Show confirmation or thank you
// This example reads a v2/checkout/orders capture response, propagated from the server
// You could use a different API or structure for your 'orderData'
var errorDetail = Array.isArray(orderData.details) && orderData.details[0];
if (errorDetail && errorDetail.issue === 'INSTRUMENT_DECLINED') {
return actions.restart(); // Recoverable state, per:
// https://developer.paypal.com/docs/checkout/integration-features/funding-failure/
}
if (errorDetail) {
var msg = 'Sorry, your transaction could not be processed.';
if (errorDetail.description) msg += '\n\n' + errorDetail.description;
if (orderData.debug_id) msg += ' (' + orderData.debug_id + ')';
return alert(msg); // Show a failure message (try to avoid alerts in production environments)
}
// Successful capture! For demo purposes:
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
var transaction = orderData.purchase_units[0].payments.captures[0];
alert('Transaction ' + transaction.status + ': ' + transaction.id + '\n\nSee console for all available details');
});
}
return (<>
{(showSpinner && isPending) && <div className="spinner"/>}
<PayPalButtons
style={style}
disabled={false}
forceReRender={[amount, currency, style]}
fundingSource={undefined}
createOrder={(data, actions) => createOrder(data, actions)}
onApprove={(data, actions) => onApprove(data, actions)}
/>
</>
);
}
export default function PayPalButton() {
return (
<div style={{ maxWidth: "750px", minHeight: "200px" }}>
<PayPalScriptProvider
options={{
"client-id": "test",
components: "buttons",
currency: "USD"
}}
>
<ButtonWrapper
currency={currency}
showSpinner={false}
/>
</PayPalScriptProvider>
</div>
);
}
And then followed the flow from paypal's github example
created their HttpClient
const checkoutNodeJssdk = require('#paypal/checkout-server-sdk');
/**
* Returns PayPal HTTP client instance with environment which has access
* credentials context. This can be used invoke PayPal API's provided the
* credentials have the access to do so.
*/
function client() {
return new checkoutNodeJssdk.core.PayPalHttpClient(environment());
}
/**
* Setting up and Returns PayPal SDK environment with PayPal Access credentials.
* For demo purpose, we are using SandboxEnvironment. In production this will be
* LiveEnvironment.
*/
function environment() {
let clientId = process.env.PAYPAL_CLIENT_ID || '<clientId>';
let clientSecret = process.env.PAYPAL_CLIENT_SECRET || '<secret>';
if (process.env.NODE_ENV === 'production') {
return new checkoutNodeJssdk.core.LiveEnvironment(clientId, clientSecret);
}
return new checkoutNodeJssdk.core.SandboxEnvironment(clientId, clientSecret);
}
async function prettyPrint(jsonData, pre=""){
let pretty = "";
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
}
for (let key in jsonData){
if (jsonData.hasOwnProperty(key)){
if (isNaN(key))
pretty += pre + capitalize(key) + ": ";
else
pretty += pre + (parseInt(key) + 1) + ": ";
if (typeof jsonData[key] === "object"){
pretty += "\n";
pretty += await prettyPrint(jsonData[key], pre + "\t");
}
else {
pretty += jsonData[key] + "\n";
}
}
}
return pretty;
}
module.exports = {client: client, prettyPrint:prettyPrint};
created their CreateOrder:
/**
* PayPal SDK dependency
*/
const checkoutNodeJssdk = require('#paypal/checkout-server-sdk');
/**
* PayPal HTTP client dependency
*/
const payPalClient = require('./PayPalClient');
/**
* Setting up the JSON request body for creating the Order. The Intent in the
* request body should be set as "CAPTURE" for capture intent flow.
*
*/
function buildRequestBody() {
return {
"intent": "CAPTURE",
"application_context": {
"return_url": "http://localhost:8081/process",
"cancel_url": "https://www.example.com",
"locale": "en-US",
"landing_page": "BILLING",
"user_action": "CONTINUE"
},
"purchase_units": [
{
"soft_descriptor": "Donation",
"amount": {
"currency_code": "USD",
"value": "220.00"
}
}
]
};
}
/**
* This is the sample function which can be sued to create an order. It uses the
* JSON body returned by buildRequestBody() to create an new Order.
*/
async function createOrder(debug=false) {
try {
const request = new checkoutNodeJssdk.orders.OrdersCreateRequest();
request.headers["prefer"] = "return=representation";
request.requestBody(buildRequestBody());
const response = await payPalClient.client().execute(request);
if (debug){
console.log("Status Code: " + response.statusCode);
console.log("Status: " + response.result.status);
console.log("Order ID: " + response.result.id);
console.log("Intent: " + response.result.intent);
console.log("Links: ");
response.result.links.forEach((item, index) => {
let rel = item.rel;
let href = item.href;
let method = item.method;
let message = `\t${rel}: ${href}\tCall Type: ${method}`;
console.log(message);
});
console.log(`Gross Amount: ${response.result.purchase_units[0].amount.currency_code} ${response.result.purchase_units[0].amount.value}`);
// To toggle print the whole body comment/uncomment the below line
console.log(JSON.stringify(response.result, null, 4));
}
return response;
}
catch (e) {
console.log(e)
}
}
/**
* This is the driver function which invokes the createOrder function to create
* an sample order.
*/
if (require.main === module){
(async() => await createOrder(true))();
}
/**
* Exports the Create Order function. If needed this can be invoked from the
* order modules to execute the end to flow like create order, retrieve, capture
* and refund(Optional)
*/
module.exports = {createOrder:createOrder};
And endpoints:
const createUsersOrder = async (res) => {
try {
let response = await createOrder();
console.log("Creating Order...");
let orderId = "";
if (response.statusCode === 201){
console.log("Created Successfully");
orderId = response.result.id;
console.log("Links:");
response.result.links.forEach((item, index) => {
let rel = item.rel;
let href = item.href;
let method = item.method;
let message = `\t${rel}: ${href}\tCall Type: ${method}`;
console.log(message);
});
let links = {};
response.result.links.forEach(function (linkObj) {
links[linkObj.rel] = {
'href': linkObj.href,
'method': linkObj.method
};
})
//if redirect url present, redirect user
if (links.hasOwnProperty('approve')) {
var returnObj = {redirect : links['approve'].href}
console.log("Returning " + returnObj)
res.send(returnObj);
} else {
console.error('no redirect URI present');
}
}
console.log("Copy approve link and paste it in browser. Login with buyer account and follow the instructions.\nOnce approved hit enter...");
return
} catch (error) {
console.log('There was an error: ', error);
}
};
app.post("/create", function(req,res) {
createUsersOrder(res);
})
Here, this is called when button is clicked, as "createOrder" method is called. I create order just like in the v1 code, and then redirect browser to the url. However when the user approves transaction on paypal, and thus is being redirected to one of
"application_context": {
"return_url": "http://localhost:8081/process",
"cancel_url": "http://localhost:8081/cancel",
"locale": "en-US",
"landing_page": "BILLING",
"user_action": "CONTINUE"
},
return url ( /process route for success ), the request DOES NOT contain payment_id, only PAYER_ID. But the payment_id ( or order_id in v2 ) is needed to capture the order.
Since i have found literally zero blogs, tutorials, guide for v2 ( only millions for v1) i am confused where to get the order id. Do i really need to save it in DB after i create the order? OR is there any other trick?
Also, the button contains onApprove method, but after creating order, the browser is redirected to paypal, and the paypal redirects user to http://localhost:8081/process endpoint - thus the onApprove method is never invoked and useless (?).
This whole flow of v2 is really confusing, is there something i am missing?
Thanks for help

With your v2 code, do not use any redirects. At all. Your button should call 2 endpoints on your server. These two endpoints should (respectively) do the API operations of creating and capturing the order, and return the JSON result in each case (the capture route can do any server-side operations like storing the transaction result in the database before forwarding the JSON result to the client caller, since the client needs to handle any capture error situations as well as showing a success message). You can find a full stack node.js example in the PayPal integration guide, but it's fine to keep your #paypal/react-paypal-js code pretty much as-is for the front end.

Related

NodeJS insert voucher code to first person who calls API

I don't know if this has a solution already but I can't find it or I don't know what to search.
I have a rest api which returns a list of products and I want to add a voucher code to the response of the first person who calls the api. I'm using redis to cache the information of the user who received the code, that expires within 15 mins.
async function addVoucherCode(response, userId) {
try {
const key = "KEY_VOUCHER_CODE";
let cachedData = await redis.get(key);
if (cachedData) {
if (cachedData.userId === userId) response.voucherCode = cachedData.voucherCode;
return;
}
const voucherCode = await createVoucherCode(userId); //call to create voucher code and save to db
if (!voucherCode) return;
await redis.setEx(key, 15 * 60, {userId, voucherCode});
response.voucherCode = cachedData.voucherCode;
} catch (err) {
console.error("[Error] addVoucherCode: ", err);
}
}
I created a function that mimics a simultaneous request, and when I checked the response, all them have a voucher code, not just the first.
async function getProducts(url, params) {
try {
const customers = [
{ id: 1, token: "Bearer eyJhbGciOi....1" },
{ id: 2, token: "Bearer eyJhbGciOi....2"},
{ id: 3, token: "Bearer eyJhbGciOi....3"},
{ id: 4, token: "Bearer eyJhbGciOi....4"}
];
const data = await Promise.all(customers.map( async customer => {
return await fetch(url + "?" + params.toString(), {
headers: {
Authorization: customer.token
},
}).then(res => res.json());
}));
data.forEach((item, indx) => {
if(item.voucherCode) {
const id = customers[indx].id;
console.log(`Customer ${id} has a voucher!!!!!!!!!!!!!`)
}
})
} catch (err) {
console.error("[Error] getProducts: ", err);
}
}
Result
Customer 1 has a voucher!!!!!!!!!!!!!
Customer 2 has a voucher!!!!!!!!!!!!!
Customer 3 has a voucher!!!!!!!!!!!!!
Customer 4 has a voucher!!!!!!!!!!!!!
I tried adding a 200ms delay inside addVoucherCode but same result. Thanks in advance for the help.
You are calling addVoucherCode in a sync loop, so it'll run 4 times in parallel (and the 4 GET commands will be issued at the same time, it'll reply with null to all of them, and all of them will call createVoucherCode).
There are 2 things you can do to fix it:
Cache the promise of createVoucherCode:
const createVoucherCodePromises = new Map();
function createVoucherCode(userId) {
if (!createVoucherCodePromises.has(userId)) {
createVoucherCodePromises.set(
userId,
_createVoucherCode(userId)
.finally(() => createVoucherCodePromises.delete(userId))
);
}
return createVoucherCodePromises.get(userId);
}
async function _createVoucherCode(userId) {
// ...
}
NOTE: this will not solve the problem if you have multiple node processes running at the same time.
Use SET with NX (won't override existing values) and GET (return existing/old value)
> SET key voucher1 NX GET
OK
> SET key voucher2 NX GET # will return the existing value without overriding it
"voucher1"
> GET key
"voucher1"

How to send request when user have only same route from React.js to Node.js?

When user exists on the route / (when user 1st time visit at route). then request send to node js server and Feed load from server to React. it is ok.
But problem is that when user move to another route and after come back to home / route then request again send to server and Feed again load without refreshing page. but I want to send request when user refresh page only and when user come back same route. then Feed data take from web storage not from server.
All the posts have been loaded. And when the user comes back on the same route then by the help of infinite scroll load post start from all the posts that have been loaded.
My code is:
//load posts for users
const [increment, setIncrement] = useState(2)
let initialPage = 0;
useEffect(() => {
async function loadPosts() {
try {
const loadPostResponse = await fetch(`${process.env.REACT_APP_API_BACKENDURL}/blob/load/all/post/${initialPage}/${increment}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + localStorage.getItem("uuid")
}
})
const loadPostData = await loadPostResponse.json()
console.log({ loadPostData }, { increment }, { initialPage })
if (loadPostResponse.status === 200) {
const Arrange = loadPostData.data.sort((a, b) => {
return b.time - a.time
})
if (loadingPostIsMount.current) {
setAllPosts(Arrange)
//set all posts into session storage
if (initialPage === 0) {
sessionStorage.setItem("_users_posts", JSON.stringify(Arrange))
}
else if (initialPage > 0) {
sessionStorage.removeItem("_users_posts")
sessionStorage.setItem("_users_posts", JSON.stringify(allPosts))
}
}
initialPage = initialPage + 2
}
} catch (err) {
}
}
loadPosts()
return () => {
loadingPostIsMount.current = false
}
}, [increment])
where increment is dependency which is hit by infinite scroll.
My infinite scroll code is
function loadMorePost() {
setIncrement(increment + 2)
}
<InfiniteScroll
dataLength={length}
next={loadMorePost}
hasMore={true}
loader={<Spinner name="three-bounce" />}
className="md:ml-[8rem] ml-[0rem]"
endMessage={
<p>loeading end</p>
}
/>
please help me.🙏🙏🙏🙏🙏

How to send push notification to Specific user in Web-push?

I have an existing web site , i just need to push notification to the site, for that i am using
Nodejs web-push package , I can able to receive notifications but i need to change it to User Specific,
For Example i want to send the notifications for the users based on the country
This is my code
client.js
const publicVapidKey = 'xxxxxx';
if ('serviceWorker' in navigator) {
console.log('Registering service worker');
run().catch(error => console.error(error));
}
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
async function run() {
console.log('Registering service worker');
const registration = await navigator.serviceWorker.
register('worker.js');
console.log('Registered service worker');
console.log('Registering push');
const subscription = await registration.pushManager.
subscribe({
userVisibleOnly: true,
// The `urlBase64ToUint8Array()` function is the same as in
// https://www.npmjs.com/package/web-push#using-vapid-key-for-applicationserverkey
applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
});
// subscription.user = $('.header-user-name').find('span').text();
console.log('Registered push');
console.log('Sending push');
await fetch('http://localhost:3000/subscribe?user='+$('.header-user-name').find('span').text(), {
method: 'POST',
body: JSON.stringify(subscription),
headers: {
'content-type': 'application/json'
}
});
console.log('Sent push');
}
Worker.js
console.log('Loaded service worker!');
self.addEventListener('push', ev => {
const data = ev.data.json();
console.log('Got push', data);
ev.waitUntil(self.registration.showNotification(data.title, {
body: 'Hello, World!',
registration_ids: [$('.header-user-name').find('span').text()]
icon: 'http://mongoosejs.com/docs/images/mongoose5_62x30_transparent.png'
}));
});
Server Code (localhost:3000/push)
app.get('/push',function(req,res) {
const payload = JSON.stringify({ title: 'Hello '+ user.name +' ' + req.query.title, });
console.log(req.query);
console.log("yahooooooooooooooooooooooooooooooooo");
webpush.sendNotification(newSubscription, payload).catch(error => {
console.error(error.stack);
});
res.send({result : 'Success'});
});
After long gap, I got a solution for this problem.
Steps to be followed:
Create an ExpressJs api to store the user subscription in to a database.
Get the user based on countries from database (you can use your own backend language I have chosen Nodejs).
Create an api which can send push notifications using given parameter like (Country, Users, Etc).
Happy Coding.
You can use the users' IPs to look up their countries by using a service like ip-api.com. After you get the country info, you can include it in your request body along with the push subscription object and send to your backend. So, you will have the opportunity to segment your subscribers and send them push notifications with different content.

Nodejs - Google API Send email doesn't group by thread

As part of small email CRM project, i have created a Nodejs app. While i send email through gmail send api, the messages are not grouped.
const sendObject = {}
output.data.messages.map(each => {
sendObject.threadId = each.threadId // ThreadID is unique.
each.payload.headers.map(head => {
if (head.name === 'From') {
sendObject.replyTo = head.value
} else if (head.name === 'Subject') {
if (head.value.indexOf('Re:') >= 0) {
sendObject.subject = head.value
} else {
sendObject.subject = 'Re: ' + head.value
}
} else if (head.name === 'Message-Id') {
sendObject.inReplyTo = head.value // The last thread messageId is inserted into In-Reply-To tag
sendObject.reference.push(head.value) // All the messageId are appended as part of References tag
}
})
})
const email_lines = []
email_lines.push('References:' + sendObject.reference.join(' '))
email_lines.push('In-Reply-To:' + sendObject.inReplyTo)
email_lines.push('Subject: ' + sendObject.subject)
email_lines.push('To:' + sendObject.replyTo)
email_lines.push('')
email_lines.push(req.body.body)
const email = email_lines.join('\r\n').trim()
let base64EncodedEmail = new Buffer(email).toString('base64')
base64EncodedEmail = base64EncodedEmail.replace(/\+/g, '-').replace(/\//g, '_')
emailConfig.gmail.users.messages.send({
userId: 'me',
threadId: sendObject.threadId,
resource: {
raw: base64EncodedEmail
}
}, async (err) => {
if (err) {
console.log('The Threads API returned an error: ' + err)
}
})
When i login to gmail through browser, i can see 2 different emails instead of one thread.
You should put threadId inside of the resource.
const response = await this.gmail.users.messages.send({
auth: this.oAuth2Client,
userId: this.gmailAddress,
uploadType: 'multipart',
resource: {
threadId: email.threadId,
raw: raw
}
})
For more details, you can check my Gmail-APIs-wrapper here

Historian for a particular participant

Is there any way in which I can get Historian for a particular participant in hyperledger-composer using node API?
I am developing an application based on hyperledger-composer using Node APIs.I want to show the history of transaction of a particular participant in his/her profile. I have created the permission.acl for that and that is working fine in playground. But when i am accessing the historian from node API it is giving complete historian of the network. I don't know how to filter that for a participant.
you can return results from REST API calls since v0.20 to the calling client application, so something like the following would work (not tested, but you get the idea). NOTE: You could just call the REST API end (/GET Trader) direct via REST with your parameter (or whatever endpoints you create for your own business network - the example below is trade-network), rather than the example of using 'READ-ONLY' Transaction processor Endpoint described below, for returning larger result sets to your client application. See more on this in the docs
NODE JS Client using APIs:
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const rp = require('request-promise');
this.bizNetworkConnection = new BusinessNetworkConnection();
this.cardName ='admin#mynet';
this.businessNetworkIdentifier = 'mynet';
this.bizNetworkConnection.connect(this.cardName)
.then((result) => {
//You can do ANYTHING HERE eg.
})
.catch((error) => {
throw error;
});
// set up my read only transaction object - find the history of a particular Participant - note it could equally be an Asset instead !
var obj = {
"$class": "org.example.trading.MyPartHistory",
"tradeId": "P1"
};
async function callPartHistory() {
var options = {
method: 'POST',
uri: 'http://localhost:3000/api/MyPartHistory',
body: obj,
json: true
};
let results = await rp(options);
// console.log("Return value from REST API is " + results);
console.log(" ");
console.log(`PARTICIPANT HISTORY for Asset ID: ${results[0].tradeId} is: `);
console.log("=============================================");
for (const part of results) {
console.log(`${part.tradeId} ${part.name}` );
}
}
// Main
callPartHistory();
//
MODEL FILE
#commit(false)
#returns(Trader[])
transaction MyPartHistory {
o String tradeId
}
READ-ONLY TRANSACTION PROCESSOR CODE (in 'logic.js') :
/**
* Sample read-only transaction
* #param {org.example.trading.MyPartHistory} tx
* #returns {org.example.trading.Trader[]} All trxns
* #transaction
*/
async function participantHistory(tx) {
const partId = tx.tradeid;
const nativeSupport = tx.nativeSupport;
// const partRegistry = await getParticipantRegistry('org.example.trading.Trader')
const nativeKey = getNativeAPI().createCompositeKey('Asset:org.example.trading.Trader', [partId]);
const iterator = await getNativeAPI().getHistoryForKey(nativeKey);
let results = [];
let res = {done : false};
while (!res.done) {
res = await iterator.next();
if (res && res.value && res.value.value) {
let val = res.value.value.toString('utf8');
if (val.length > 0) {
console.log("#debug val is " + val );
results.push(JSON.parse(val));
}
}
if (res && res.done) {
try {
iterator.close();
}
catch (err) {
}
}
}
var newArray = [];
for (const item of results) {
newArray.push(getSerializer().fromJSON(item));
}
console.log("#debug the results to be returned are as follows: ");
return newArray; // returns something to my NodeJS client (called via REST API)
}

Resources