Load confirmation URL in Embedded app itself in Shopify - node.js

I have an embedded app in shopify which is an paid app ,Once user approves the billing ,i want the app to show the confirmation url in the embedded app itself instead it loads externally.
getsubscriptionurl.js
export const getSubscriptionUrl = async (ctx, shop) => {
const { client } = ctx;
console.log(`process.env.HOST - ${process.env.HOST}`);
console.log(`shop - ${shop}`);
console.log(`${process.env.HOST}/?shop=${shop}`);
const confirmationUrl = await client
.mutate({
mutation: RECURRING_CREATE(),
variables: {
returnUrl: `www.abc.com`,
}
})
.then(response => response.data.appSubscriptionCreate.confirmationUrl);
console.log("me "+ confirmationUrl);
return ctx.redirect(confirmationUrl);
};
server.js
app.prepare().then(async () => {
const server = new Koa();
const router = new Router();
server.keys = [Shopify.Context.API_SECRET_KEY];
server.use(
createShopifyAuth({
async afterAuth(ctx) {
// Access token and shop available in ctx.state.shopify
const { shop, accessToken, scope } = ctx.state.shopify;
const host = ctx.query.host;
ACTIVE_SHOPIFY_SHOPS[shop] = {scope:scope,accessToken:accessToken};
const response = await Shopify.Webhooks.Registry.register({
shop,
accessToken,
path: "/webhooks",
topic: "APP_UNINSTALLED",
webhookHandler: async (topic, shop, body) =>
delete ACTIVE_SHOPIFY_SHOPS[shop],
});
if (!response.success) {
console.log(
`Failed to register APP_UNINSTALLED webhook: ${response.result}`
);
}
// Redirect to app with shop parameter upon auth
// ctx.redirect(`/?shop=${shop}&host=${host}`);
server.context.client = await handlers.createClient(shop, accessToken);
await handlers.getSubscriptionUrl(ctx, shop);
},
})
);

You can't basically show the confirmation URL in your app, Shopify won't trust app developers to take sensitive info like payment details, so must open the confirmation URL into a new tab, where the merchant is viewing a Shopify payment page(made by shopify) that contains the payment details to be entered and on confirm the page will redirect the merchant to the return URL as you specified before.
For testing purposes
you can send a test param within the query to allow you to test without entering any payment details
const CREATE_SUB_MUTATION_RECURRING_ONLY = gql`
mutation RecurringSubscription(
$returnUrl: URL!
$test: Boolean!
$planName: String!
$amount: Decimal!
) {
appSubscriptionCreate(
test: $test
name: $planName
returnUrl: $returnUrl
lineItems: [
{
plan: {
appRecurringPricingDetails: {
price: { amount: $amount, currencyCode: USD }
interval: EVERY_30_DAYS
}
}
}
]
) {
userErrors {
field
message
}
confirmationUrl
appSubscription {
id,
currentPeriodEnd
}
}
}
`;
Now to test just pass true to test
result = await graphQlClient?.mutate({
mutation: CREATE_SUB_MUTATION_RECURRING_ONLY,
variables: {
returnUrl,
test,
planName: PLANS_DATA[planName].planName,
amount: PLANS_DATA[planName].price,
},
});

Related

How to automate the login on SAP IdP in an end-to-end test

Our backend API's auth method's been replaced to OAuth 2.0.
Now we would like to write end2end auth testing with different business users.
My idea is to write testing code in the BTP, which will call the backend OAuth enabled SAP service.
Just followed the e2e test tutorial using nightwatch and cucumber.
But now the logon page has been changed to the SAP IDP logon page.
Do you know how to automate the login&logout for this idp logon page?
Thanks a lot!
idp logon page
I could not find the element name of username and the password in the idp logon page.
In the JS SDK We use puppeteer to fetch a token programatically. In the end it also provides the user name and password to the IdP. Here is a sample:
import https from 'https';
import { createLogger } from '#sap-cloud-sdk/util';
import { Service, parseSubdomain } from '#sap-cloud-sdk/connectivity/internal';
import puppeteer from 'puppeteer';
import axios from 'axios';
import { UserNamePassword } from './test-parameters';
const logger = createLogger('e2e-util');
export interface UserTokens {
access_token: string;
refresh_token: string;
}
export interface GetJwtOption {
redirectUrl: string;
route: string;
xsuaaService: Service;
subdomain: string;
userAndPwd: UserNamePassword;
}
export async function getJwt(options: GetJwtOption): Promise<UserTokens> {
const { redirectUrl, route, userAndPwd, xsuaaService, subdomain } = options;
const xsuaaCode = await getAuthorizationCode(redirectUrl, route, userAndPwd);
return getJwtFromCode(xsuaaCode, redirectUrl, xsuaaService, subdomain);
}
export async function getJwtFromCode(
xsuaaCode: string,
redirectUri: string,
xsuaaService: Service,
subdomain: string
): Promise<UserTokens> {
let httpsAgent: https.Agent;
const params = new URLSearchParams();
params.append('redirect_uri', `${redirectUri}/login/callback`);
params.append('code', xsuaaCode);
params.append('grant_type', 'authorization_code');
params.append('client_id', xsuaaService.credentials.clientid);
if (xsuaaService.credentials.clientsecret) {
params.append('client_secret', xsuaaService.credentials.clientsecret);
httpsAgent = new https.Agent();
} else {
httpsAgent = new https.Agent({
cert: xsuaaService.credentials.certificate,
key: xsuaaService.credentials.key
});
}
const url = xsuaaService.credentials.clientsecret
? xsuaaService.credentials.url
: xsuaaService.credentials.certurl;
const subdomainProvider = parseSubdomain(url);
const urlReplacedSubdomain = url.replace(subdomainProvider, subdomain);
const response = await axios.post(
`${urlReplacedSubdomain}/oauth/token`,
params,
{
httpsAgent
}
);
if (!response.data.access_token) {
throw new Error('Failed to get the JWT');
}
logger.info(`Obtained JWT for ${redirectUri}.`);
return {
access_token: response.data.access_token,
refresh_token: response.data.refresh_token
};
}
async function getAuthorizationCode(
url: string,
route: string,
userAndPwd: UserNamePassword
): Promise<string> {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox']
});
const page = await browser.newPage();
await page.setRequestInterception(true);
// Catch all failed requests like 4xx..5xx status codes
page.on('requestfailed', request => {
if (!request.failure()!.errorText.includes('ERR_ABORTED')) {
logger.error(
`url: ${request.url()}, errText: ${
request.failure()?.errorText
}, method: ${request.method()}`
);
}
});
// Catch console log errors
page.on('pageerror', err => {
logger.error(`Page error: ${err.toString()}`);
});
page.on('request', request => {
if (request.url().includes('/login/callback?code=')) {
request.abort('aborted');
} else {
request.continue();
}
});
try {
await Promise.all([
await page.goto(`${url}/${route}`),
await page.waitForSelector('#j_username', {
visible: true,
timeout: 5000
})
]);
} catch (err) {
throw new Error(
`The #j_username did not show up on URL ${url}/${route} - perhaps you have the identityProvider in the xs-security.json of your approuter?`
);
}
await page.click('#j_username');
await page.keyboard.type(userAndPwd.username);
const passwordSelect = await page
.waitForSelector('#j_password', { visible: true, timeout: 1000 })
.catch(() => null);
// For ldap IdP one step in between with navigation to second page
if (!passwordSelect) {
await Promise.all([
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
page.click('button[type="submit"]')
]);
}
await page.click('#j_password');
await page.keyboard.type(userAndPwd.password);
const [authCodeResponse] = await Promise.all([
page.waitForResponse(response =>
response.url().includes('oauth/authorize?')
),
page.click('button[type="submit"]')
]);
await browser.close();
const parsedLocation = new URL(authCodeResponse.headers().location);
if (!parsedLocation.searchParams.get('code')) {
throw new Error('Final location redirect did not contain a code');
}
return parsedLocation.searchParams.get('code');
}
Best
Frank

Razorpay not returning payment_id,order_id etc. upon successfull payment

I have one function that is responsible of creating order using razorpay and verify that order .
The code for that function is : -
const paymentMethod = async ()=>{
if(!isAuthenticated())
{
toast.error('Please signin to continue');
history.push('/signin')
// return <Redirect to='/signin' />
return;
}
try{
// console.log(JSON.stringify(total));
const obj = {
amount:total
}
let data = await fetch('http://localhost:3100/api/checkout',{
method:'POST',
headers:{
"content-type":"application/json",
Accept:"application/json"
},
body: JSON.stringify(obj)
})
let {order} = await data.json()
console.log(order)
console.log(order.amount)
const options = {
key: "rzp_test_cXj3ybg8fawS9Y",
amount: order.amount,
currency: "INR",
name: "YO! Merchandise",
description: "Pay Please",
image:yo,
order_id: order.id,
callback_url: "http://localhost:3100/api/paymentverification",
prefill: {
name: "Aakash Tiwari",
email: "tiwaryaakash00#gmail.com",
contact: "8750043604"
},
notes: {
address: "Razorpay Corporate Office"
},
theme: {
color: "#13C962"
}
};
let rzp1 = await new window.Razorpay(options);
rzp1.open();
}
catch(err){
console.log(err)
}
}
But this function when call callback_url upon successfull payment it is not passing the payment_id,order_id etc. other neccessary details.
when i try to console.log there the req.body is always empty.

Autocomplete slash command appear only one admin

Description
I created slash command bot with bot, applications.commands scopes. And create slash command for guild, and command not showed so I create commands as application scope. And still not showing
I tried to kick out my bot and re enter url but not worked...
What is the solution plz! Thank you
Steps to Reproduce
const serverless = require("serverless-http");
const express = require("express");
const app = express();
const { CreateRateUseCase } = require("./core/usecase/createRateUseCase");
const nacl = require("tweetnacl");
const getRawBody = require("raw-body");
const { DiscordBot } = require("./discordBot");
// const { verifyKeyMiddleware } = require("discord-interactions");
require("dotenv").config({ path: `./.env.${process.env.NODE_ENV}` });
app.post(
"/interactions",
// verifyKeyMiddleware(process.env.DISCORD_PUBLIC_KEY),
async (req, res, next) => {
try {
console.log(`req : `);
console.log(req);
console.log(`body ${req.body} ${JSON.stringify(req.body)}`);
const rawBody = await getRawBody(req);
console.log(`rawBody ${rawBody} ${typeof rawBody}`);
const body = JSON.parse(rawBody);
if (
process.env.NODE_ENV === "dev" ||
process.env.NODE_ENV === "production"
) {
const signature = req.get("X-Signature-Ed25519");
console.log(`signature ${signature}`);
const timestamp = req.get("X-Signature-Timestamp");
console.log(`timestamp ${timestamp}`);
const isVerified = nacl.sign.detached.verify(
Buffer.from(timestamp + rawBody),
Buffer.from(signature, "hex"),
Buffer.from(process.env.DISCORD_PUBLIC_KEY, "hex")
);
console.log(`isVerified ${isVerified}`);
if (!isVerified) {
console.log("Failed verification");
return res.status(401).end("invalid request signature");
}
if (body.type === 1) {
console.log("Handling validation test request");
return res.status(200).send({ type: 1 });
}
}
if (body.type === 2) {
if (
body.channel_id !== process.env.DISCORD_CHANNEL_ID_KOR &&
body.channel_id !== process.env.DISCORD_CHANNEL_ID_EN
) {
console.log(`channel id ${body.channel_id}`);
console.log(
"This command is only available in the COMMUNITY category"
);
res.status(200).send({
type: 4,
data: {
content: `This command is only available in the 'COMMUNITY' category. 😒`,
},
});
return;
}
const discordBot = new DiscordBot();
const result = await discordBot.execute(body);
console.log(`result ${JSON.stringify(result)}`);
res.status(200).send(result);
console.log("reply done");
}
return;
} catch (e) {
console.error(e.message);
return res.send("Error handling verification");
}
}
);
deploy server on aws lambda
OAuth2 -> URL Generator, check bot, applications.commands and enter url then select server.
check SERVER MEMBERS INTENT, MESSAGE CONTENT INTENT
enter api gateway url to INTERACTIONS ENDPOINT URL as https://xyz.amazonaws.com/interactions/
create slash commands
const { Client, Intents } = require("discord.js");
const client = new Client({
intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES],
});
console.log(`NODE_ENV ${process.env.NODE_ENV}`);
require("dotenv").config({ path: `./.env.${process.env.NODE_ENV}` });
const korCommandList = {
γ……γ…‡γ„Ή: {
description: "예치 수읡λ₯  응닡",
},
수읡λ₯ : {
description: "예치 수읡λ₯  응닡",
},
예치수읡λ₯ : {
description: "예치 수읡λ₯  응닡",
},
};
const enCommandList = {
depositapy: {
description: "response deposit yield",
},
yield: {
description: "response deposit yield",
},
apy: {
description: "response deposit yield",
},
deposityield: {
description: "response deposit yield",
},
};
client.on("ready", async () => {
const promises = [];
const objs = Object.assign(korCommandList, enCommandList);
console.log(
`process.env.DISCORD_BOT_CLIENT_ID ${process.env.DISCORD_BOT_CLIENT_ID}`
);
console.log(`process.env.DISCORD_GUILD_ID ${process.env.DISCORD_GUILD_ID}`);
for (const key in objs) {
const p = await client.api
.applications(process.env.DISCORD_BOT_CLIENT_ID)
// .guilds(process.env.DISCORD_GUILD_ID)
.commands.post({
data: {
name: key,
description: objs[key].description,
},
});
promises.push(p);
}
const result = await Promise.all(promises);
console.log(result);
client.destroy();
});
client.login(process.env.DISCORD_BOT_TOKEN);
after hours, or day type / on discord app. normal user can not see commad list, but admin can see.
Expected Behavior
Every user on server with bot can see slash command list.
Current Behavior
Admin can see slash command lists, but normal user can not see.
Screenshots/Videos
text box by server admin
text box by server normal user, slash commands not displayed
Client and System Information
browser admin : chrome on mac
user : discord app on mac
lib

NUXTJS SSR: I have problems with the nuxtServerInit, the req.headers.cookie of the nuxtServerInit is undefined and the cookie is already in my browser

I am new to NuxtJS and have a problem, I am trying to get the token of the user who logged in from the website, the token is stored in the cookie, but, when I start or reload the website page (made with nuxt ssr (universal )), nuxtServerInit should and does start, but req.headers.cookie says it is undefined, so the data cannot be loaded into the store. If I reload the page, the cookie is still in the browser and everything is perfect, the problem is that the req.headers.cookie is: undefined, why? Ahhh, and in development it works perfectly, but in production it doesn't work (in nuxtServerInit the req.headers.cookie is not defined)
I am using Firebase Hosting, Cloud Features, Authentication, Cloud Firestore.
Here is the code:
// store/index.js
import { getUserFromCookie, getUserFromSession } from '../helpers/index.js'
export const actions = {
async nuxtServerInit({ app, dispatch }, { req, beforeNuxtRender }) {
console.log('req.headers.cookie: ' + req.headers.cookie)
console.log('req.session: ', req.session)
if (process.server) {
const user = getUserFromCookie(req)
console.log('process.server: ' + process.server)
console.log('user: ', user)
if (user) {
await dispatch('modules/user/setUSER', {
name: !!user.name ? user.name : '',
email: user.email,
avatar: !!user.picture ? user.picture : '',
uid: user.user_id
})
await dispatch('modules/user/saveUID', user.user_id)
} else {
await dispatch('modules/user/setUSER', null)
await dispatch('modules/user/saveUID', null)
}
}
}
}
// helpers/index.js
import jwtDecode from 'jwt-decode'
var cookieparser = require('cookieparser')
export function getUserFromCookie(req) {
if(req.headers.cookie){
const parsed = cookieparser.parse(req.headers.cookie)
const accessTokenCookie = parsed.access_token
if (!accessTokenCookie) return
const decodedToken = jwtDecode(accessTokenCookie)
if (!decodedToken) return
return decodedToken
}
return null
}
// pages/auth/signup.vue
<script>
import { mapActions } from 'vuex'
export default {
data () {
return {
email: '',
password: '',
renderSource: process.static ? 'static' : (process.server ? 'server' : 'client')
}
},
middleware: ['handle-login-route'],
methods: {
...mapActions('modules/user', [ 'login' ]),
async signUp () {
try {
const firebaseUser = await this.$firebase.auth().createUserWithEmailAndPassword(this.email, this.password)
await this.writeUserData(firebaseUser.user.uid, firebaseUser.user.email)
await this.login(firebaseUser.user)
this.$router.push('/protected')
} catch (error) {
console.log(error.message)
}
},
writeUserData (userId, email) {
const db = this.$firebase.firestore()
return db.collection("Usuarios").doc(userId).set({
email: email
})
}
}
}
</script>
// store/modules/user.js
export const actions = {
async login({ dispatch, state }, user) {
console.log('[STORE ACTIONS] - login')
const token = await this.$firebase.auth().currentUser.getIdToken(true)
const userInfo = {
name: user.displayName,
email: user.email,
avatar: user.photoURL,
uid: user.uid
}
Cookies.set('access_token', token) // saving token in cookie for server rendering
await dispatch('setUSER', userInfo)
await dispatch('saveUID', userInfo.uid)
console.log('[STORE ACTIONS] - in login, response:', status)
},
async logout({ commit, dispatch }) {
console.log('[STORE ACTIONS] - logout')
await this.$firebase.auth().signOut()
Cookies.remove('access_token');
commit('setUSER', null)
commit('saveUID', null)
},
saveUID({ commit }, uid) {
console.log('[STORE ACTIONS] - saveUID')
commit('saveUID', uid)
},
setUSER({ commit }, user) {
commit('setUSER', user)
}
}
Thanks a lot! :D
if you are hosting it on Firebase you should rename your cookie to "__session", that's the only name you could use on Firebase. Their documentation should make it very clear!
Change the following part:
// helpers/index.js
const parsed = cookieparser.parse(req.headers.cookie)
const accessTokenCookie = parsed.__session
// store/modules/user.js
Cookies.set('__session', token)

Stripe Payment of Course using FireStore Cloud Function

So I have made a page where students will get redirected to after choosing in which course they want to register themselves and on that page the students will provide their personal information and right after they'll click on 'continue for registration' button a stripe checkout form will open that will charge them the amount of that particular course and only then those students will get registered in the cloud firestor database collection. So i started learning from these tutorials link. However my cloud functions are written in NodeJS backend so i looked for some sample stripe apis for payment and found these sample apis on this github repo.
I am not sure how to hit these apis or if these are the right apis for me because my other functions are a little bit different then these stripe functions.
I would very much appreciate your help to know how can i implement this functionality. I am pasting the stripe fucntions here too.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const logging = require('#google-cloud/logging')();
const stripe = require('stripe')(functions.config().stripe.token);
const currency = functions.config().stripe.currency || 'USD';
// [START chargecustomer]
// Charge the Stripe customer whenever an amount is created in Cloud Firestore
exports.createStripeCharge = functions.firestore.document('stripe_customers/{userId}/charges/{id}').onCreate(async (snap, context) => {
const val = snap.data();
try {
// Look up the Stripe customer id written in createStripeCustomer
const snapshot = await admin.firestore().collection(`stripe_customers`).doc(context.params.userId).get()
const snapval = snapshot.data();
const customer = snapval.customer_id
// Create a charge using the pushId as the idempotency key
// protecting against double charges
const amount = val.amount;
const idempotencyKey = context.params.id;
const charge = {amount, currency, customer};
if (val.source !== null) {
charge.source = val.source;
}
const response = await stripe.charges.create(charge, {idempotency_key: idempotencyKey});
// If the result is successful, write it back to the database
return snap.ref.set(response, { merge: true });
} catch(error) {
// We want to capture errors and render them in a user-friendly way, while
// still logging an exception with StackDriver
console.log(error);
await snap.ref.set({error: userFacingMessage(error)}, { merge: true });
return reportError(error, {user: context.params.userId});
}
});
// [END chargecustomer]]
// When a user is created, register them with Stripe
exports.createStripeCustomer = functions.auth.user().onCreate(async (user) => {
const customer = await stripe.customers.create({email: user.email});
return admin.firestore().collection('stripe_customers').doc(user.uid).set({customer_id: customer.id});
});
// Add a payment source (card) for a user by writing a stripe payment source token to Cloud Firestore
exports.addPaymentSource = functions.firestore.document('/stripe_customers/{userId}/tokens/{pushId}').onCreate(async (snap, context) => {
const source = snap.data();
const token = source.token;
if (source === null){
return null;
}
try {
const snapshot = await admin.firestore().collection('stripe_customers').doc(context.params.userId).get();
const customer = snapshot.data().customer_id;
const response = await stripe.customers.createSource(customer, {source: token});
return admin.firestore().collection('stripe_customers').doc(context.params.userId).collection("sources").doc(response.fingerprint).set(response, {merge: true});
} catch (error) {
await snap.ref.set({'error':userFacingMessage(error)},{merge:true});
return reportError(error, {user: context.params.userId});
}
});
// When a user deletes their account, clean up after them
exports.cleanupUser = functions.auth.user().onDelete(async (user) => {
const snapshot = await admin.firestore().collection('stripe_customers').doc(user.uid).get();
const customer = snapshot.data();
await stripe.customers.del(customer.customer_id);
return admin.firestore().collection('stripe_customers').doc(user.uid).delete();
});
Angular Test Component(Not Sure about this)
import * as firebase from 'firebase';
import { Component, Input, OnInit, ViewChild } from '#angular/core';
import { AngularFireFunctions } from '#angular/fire/functions';
import { PaymentService } from './payment.service';
import { environment } from '../../../../environments/environment';
#Component({
selector: 'ngx-payment-request',
templateUrl: './payment-request.component.html',
styleUrls: ['./payment-request.component.scss'],
providers: [PaymentService]
})
export class PaymentRequestComponent implements OnInit {
// #Input() amount: 100;
// #Input() label: 'Course';
elements: any;
paymentRequest: any;
prButton: any;
handler: any;
amount: number = 500;
confirmation: any;
loading = false;
#ViewChild('payElement', { static: true }) payElement;
constructor(private pmt: PaymentService, private functions: AngularFireFunctions) { }
ngOnInit() {
// this.loadStripe();
// this.pmt.showId();
this.pmt.showId();
this.handler = StripeCheckout.configure({
// key: environment.stripeKey,
image: 'https://oc1.ocstatic.com/images/logo_small.png',
locale: 'auto',
token: token => {
this.pmt.processPayment(token, this.amount)
},
source: async (source) => {
this.loading = true;
const user = this.pmt.showId();
firebase.functions().useFunctionsEmulator('hxxxxxx.net')
const fun = this.functions.httpsCallable('stripeCreateCharge');
this.confirmation = await fun({ source: source.id, amount: this.amount}).toPromise();
this.loading = false;
}
});
}
handlePayment(e) {
const user = this.pmt.showId();
this.handler.open({
name: 'FireStarter',
description: 'Pay your Dues',
amount : this.amount,
});
e.preventDefault();
}
}

Resources