Google Pay dialog back button causes error from loadPaymentData - google-pay

I have the following code on my website.
My problem is, the user hits "Pay with Google" button to trigger the code below. This shows the Google Pay popup (which is loaded from the loadPaymentData()).
If the user proceeds with the payment, then everything works great.
If the user clicks on the back button within the popup, then this is seen as an error and drops into the catch(function(err)) below. This paymentData or payload is not logged in the console. This is obviously not an error - the user just wants to select a different payment method.
How do I catch if the user has hit the back button within the popup?
paymentsClient.loadPaymentData(paymentDataRequest)
.then(function (paymentData) {
console.log("paymentData", paymentData);
// TODO
}).then(function (payload) {
console.log("payload", payload);
// Send result nonce to server
}).catch(function (err) {
console.log('pay error', err);
});

You can check to see if the payment sheet was cancelled by inspecting err.statusCode === 'CANCELED'.
See documentation: https://developers.google.com/pay/api/web/reference/client#errors_1

Related

Cypress uncaught:exception handler not working with Magic.link flow

I'm using Cypress to test a login flow that uses Magic.link auth on a mobile Web device, which is encountering the ResizeObserver loop limit exceeded error, as it tries to navigate the Google Auth forms. I've looked at numerous posts, and played around with my test, but it seems the handler is not working.
The recommended Google Authentication from the Cypress docs is insufficient, because with Magic, the flow is initiated by a call to magic.oauth.loginWithRedirect, hence I was hoping to drive the process via the UI directly.
You'll see I added a test to ensure the password input is visible. Now the exception is being thrown at that part of the test. If I remove that check the error occurs on the next step where I try to type the password.
describe('my auth flow', () => {
it('can auth with google', () => {
// click login button from my site
cy.get('button')
.contains('sign-in')
.click();
cy.origin('https://accounts.google.com', () => {
// enter email address
cy.get('input[type=email]')
.type('myuser#mydomain.com');
cy.get('button')
.find('span')
.contains('Next')
.click();
// wait for password page to show
cy.get('#password')
.should('exist')
.and('be.visible'); // error here...
// enter password
// error here if above visibility check removed
cy.get('#password input[type=password]')
.type('mypassword');
cy.get('button')
.find('span')
.contains('Next')
.click();
});
});
});
In support/commands.js, I've added the global error handler, which should handle all uncaught exceptions according to the documentation.
Cypress.on(
'uncaught:exception',
(err) => false
);
Magic does have a test mode, however I really don't want to bypass the login flow. Ideally I could exercise the login flow without hacks for testing.
The cy.origin() command is an isolated sandbox with different document and window to the primary domain.
Try adding the exception handler inside the origin command (presuming the error is happening while on the google domain).
cy.origin('https://accounts.google.com', () => {
Cypress.on('uncaught:exception', (err) => false)

expo-mail-composer is working properly on my emulator as well as on my actual device, but not on the client's

This is quite possibly user error, although I cannot really discern how that would be the case here. I have a react native app that has the feature to record and then email videos. It works pretty simply, you press the button to enter the in-app camera, press record to record and stop to stop. Once stopped, the video is saved to the phone, and then two buttons appear; 'cancel' and 'send video'
When send video is pressed, a email composer pops up with some pre-filled information, and you can compose and send an email off of your phone just like you normally would. This works all as expected on my end, but when a client presses the 'send video' button, nothing happens. No error, no email pop up, just nothing.
I've confirmed that she signed up with her email, I've investigated the database and that's true-- so I don't quite understand what could be missing. I tried an emulator for her exact device type and it worked well, so this has to be user error or something with permissions, right?
This is the function that gets run when 'send video' is clicked...
// Sends the Video, triggers the message, opens the modal
function handleSendVid(){
saveVidToLib()
.then(resolved => {
emailVid(resolved)
})
.then(() => {
if (user.role === "GUARDIAN"){
handleSendMessage()
}
})
}
With emailVid() looking like this...
// Emails the now saved video
async function emailVid(video){
MailComposer.composeAsync({
recipients: [therapist.email],
subject: `Video Of Exercise From: ${user.firstName} ${user.lastName}`,
body: "Beta Testing",
attachments: [videoPath]
})
.catch((err) => console.log(err))
.then(() => {
setEmailSent(true)
})
}

Supabase auth.update() email_refresh_token workflow

When I want to let an authenticated user change their email address I use auth.update(), found here https://supabase.com/docs/reference/javascript/auth-update
I'm using just a magic link auth, btw.
Right now my project is set up for only one email confirmation.
Im using vue 3 with vu-router v4
When I run this function
async handleUpdateUser(newEmail: string) {
try {
const { user, error } = await supabase.auth.update({
email: newEmail,
});
if (error) throw error;
} catch (error) {
console.log(error);
}
},
The onAuthStateChange that I log shows as USER_UPDATED, the users.auth table on the server shows the new stuff, and a new_email prop is seen on the user object in the console
My question is how do I make the new_emial into just email.
the link that is sent to the new email is as so
https://correct_info_is_here.supabase.co/auth/v1/verify?token=there_is_a_token_here&type=email_change&redirect_to=http://localhost:3000/
When I click the link, it redirects to a new window just like it does when an initial sign up happens, but other than that nothing changes.
I found this answer,
https://github.com/supabase/supabase/discussions/1763
but I have no idea how to implement that procedure. The only way figured out how to get that token is by an rpc function on the client , but I don't know what to do with that token after I receive it.
Also I might add when I use vue-router, I log the to and from properties of the beforeEach like so
router.beforeEach(async (to, from) => {
console.log("to", to);
console.log("from", from);...
When I load the confirmation link, it doesn't show anything useful like it does when its redirected form the original sign up link.
Any help would greatly be appreciated.
I ran into a similar issue, and in my case, it came down to the "Double confirm email changes" setting in Supabase Studio.
From the Supabase docs for supabase.auth.update:
Email updates will send an email to both the user's current and new email with a confirmation link by default. To toggle this behavior off and only send a single confirmation link to the new email, toggle "Double confirm email changes" under "Authentication" -> "Settings" off.
So what may be happening is that when you request the email change, the user receives two emails: one at the old email address, and one at the new email address. The user must click both links in order for that user's email column to actually be updated.
At least, this was the issue in my case — I hope it helps!

Vue,js used with Supabase - can't update signIn button after logging in with Oauth

async handleSignInSignOutButtonClick() {
if (!this.isSignedIn) {
supabase.auth.signIn({ provider: "google" });
this.$store.commit("signIn", supabase.auth.session());
window.location.reload();
return;
}
await this.$store.commit("signOut");
supabase.auth.signOut();
window.location.reload();
},
The above function is triggered by a sign-in button, which is supposed to become a sign-out button and the icon of the user after logging in.
When The function fires, supabase redirects me to Google OAuth consent screen. However, after logging in and redirecting back to my app, the sign-in button stays there until I manually refresh the page.
What is wrong with my code...
There are a couple of things going on that you need to be aware of. For starters you are reloading your page when you don't need to in the handleSignInSignOutButtonClick() function.
When the authentication process begins, your app will be redirected to Google OAuth consent screen as you have discovered. Once the authentication is complete, you will be redirected back to your app and the reload occurs automatically.
The second point is that you can make use of the supabase.auth.onAuthStateChange() event to help you. My suggestion would be to listen for this event when you create your supabase client so it listens for the duration of your app instance. During that event handling, you can assign the user to the store (or anywhere you want to save the user data) based upon the state change. Your app can be reactive to state changes.
In your supabase client setup code:
const supabaseUrl = process.env.SUPABASE_URL // your supabaseUrl
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY // your supabaseKey
const supabase = createClient(supabaseUrl, supabaseAnonKey)
/**
* Set up the authentication state change listener
*
* #param {string} event The event indicates what state changed (SIGN_IN, SIGN_OUT etc)
* #param {object} session The session contains the current user session data or null if there is no user
*/
supabase.auth.onAuthStateChange((event, session) => {
const user = session?.user || null;
// Save your user to your desired location
$store.commit("user", user);
});
Now you have your user data being saved whenever the user logs in and a null being set for the user data when the user logs out. Plus any page refreshes are handled by the change state event listener or any other instance that might change the user state. For example, you could have other login or logout buttons and the single listener would pick them up.
Next is to deal with the actual process of logging in or out. In your component Vue file (from your example):
async handleSignInSignOutButtonClick() {
if ($store.state.user === null) {
await supabase.auth.signIn(
{ provider: "google" },
{ redirectTo: "where_to_go_on_login" }))
} else {
await supabase.auth.signOut()
$router.push("your_logged_out_page")
}
}
Finally for your button change state to indicate logged in or logged out, you can simply observe the store user state.
<button v-if="user">Sign Out</button>
<button v-else>Sign In</button>
This way your button will update whenever the user state changes. The user state changes whenever a user logs in or out, and your code is much more compact and readable.
Once final observation that you may already be doing anyway. I would recommend that you put all of your authentication code into a single file and expose the log in and log out functions for your app use as an export to use in component files. This way everything to do with login and logout is handled in a single location and this code is abstracted away from the component file. If you ever wanted to switch from Supabase you could easily update one or two files and everything else would just keep working.

Google+ signout without dialog popup

I have a signout button on my page that I'm initiating this way:
$('#logout').click(function() {
gapi.auth.signIn({
'callback': function(authResult) {
if (authResult['status']['signed_in']) {
gapi.auth.signOut();
} else {
// second pass, signout succesful
}
}
})
});
This ends up making two calls out to Google (first to validate that user is already logged in, second to sign out), thus the two passes through the callback code. This also causes the Google+ login window to briefly popup.
Is there a way to just call gapi.auth.signOut() directly without the signIn step? I have the user's Google+ id (and also access_token), if that helps.
You don't need to call the gapi.auth.signIn() every time you want to sign out. just call the gapi.auth.signOut() from anywhere to initiate the sign-out process from your app (but still signed in to Google in other tabs, which is good practice).
Example would be to just attach the gapi.auth.signOut() event to an onclick event on a button;
<button onclick="gapi.auth.signOut();">Sign out</button>