I want to test my application's handling of webhook events from stripe when a subscription payment has been made (or failed). Here is what I've tried so far:
Set up a new subscription
Update user's credit card to be the one that can be added to an account, but will fail to actually be charged
Change the trial end date to be in one second
Wait a few seconds expecting the webhook to be sent
However, According to the documentation:
If you have configured webhooks, the invoice will wait until one hour after the last webhook is successfully sent (or the last webhook times out after failing).
One hour is a long time to wait, since I am trying to do this as part of an automated integration test suite.
One suggestion (from IRC) is to fake out the webhook request, so that my integration test sends the event, instead of Stripe sending it. However, since Stripe doesn't include any sort of HMAC in the webhooks, I can't trust the data in the payload. So, my application just takes the event ID from the webhook payload and fetches the event from the Stripe API:
If security is a concern, or if it's important to confirm that Stripe sent the webhook, you should only use the ID sent in your webhook and should request the remaining details from the API directly.
This will obviously not work if I am trying to inject fake events for my test (by design).
What are the best practices for testing this sort of scenario?
It seems there isn't a perfect way to do this. As suggested by #koopajah in a comment, I added a configuration value in my application that will disable fetching the event from Stripe, and instead just trust the event data in the webhook. This allows me to test my flow in almost the same way as it would work on production, since the event data in the webhook and the event fetched from Stripe are identical (assuming it is an authentic webhook request :)
Unless/until Stripe includes an HMAC signature in the webhook request to authenticate that it came from them, I think this is the best way to solve the problem.
One hour is a long time to wait, since I am trying to do this as part of an automated integration test suite.
You can shorten the wait by going to the invoice and selecting the "Charge customer" button, as shown below.
Related
I have a high traffic WooCommerce Subscriptions site. I need to call a 3rd party API every time an order is generated and send information about the customer (if they still have an active subscription or not), this includes new and renewal orders.
For this purpose I choose the "woocommerce_subscription_status_updated" hook. It fires every time a subscription changes state, lets me know what the current state is and then I can send that info to the API.
The issue is that when a subscription gets renewed successfully, this hook is fired twice (once if the renewal fails). Subscription get changed from "active" to "on-hold" and then "on-hold" to "active" (this is how Wc Subscription renews a subscription).
The API gets called twice even though it did not needed to. What would be a better way to implement this?
I think you can set a CRON with storing the last checked date time and get a list of data whose status are updated since the last time and call an API in some interval through CRON if it works for you.
I'm using calendar API to pull events of users in an organization that had installed my GSuite app as a service account (it just means that I have access to all the users in the organization).
I noticed some weird behavior - let's say I have an event with 2 participants - a and b (both are users in the company). when I use the calendar API events.list to pull event for a I get this event, and b is listed in the attendee list with responseStatus of 'accepted'.
When I pull the events for the same period of time for user b, I do not get this meeting, nor when I try to pull a specific event using the meeting id (event.get API). I also used showDeleted=True but still didn't get the event back when pulling the events of b (I'm pretty sure if the event would have been deleted then the responseStatus would have been 'decline').
What could be the reason for this behavior? I tried to reproduce it using two emails that I have access to, and I was not successful.
Since those are emails of my client I cannot just come up to them and ask what happened with this meeting.
This is the request I'm making (using python google API client):
from googleapiclient.discovery import build as Build
service = Build("calendar", "v3", credentials=credentials, cache_discovery=False)
service.events().list(calendarId='primary', timeMin=min_time, timeMax=max_time, pageToken=page_token, singleEvents=True).execute()
First I'm building the service using the service account credentials (delegating permissions to the current email), and then making the request to pull the events.
The min_time,max_time are strings like this - 2020-10-19T08:00:00+0000.
The pageToken starts as None but will change if there is a nextPageToken in the response.
This is the request I made to try and get the meeting of the user that appear as an attendee when pulling the event using another user's email & credentials:
service.events().get(calendarId='primary', eventId=<meeting_id>).execute()
But I'm getting 404 event not found response (This API returns deleted events - I've seen it)
I'm pulling all of the events correctly, and only saw this case on small number of the events (less than 5% of the meetings).
Any idea what could cause this?
Thanks!
We're tracking some resources in google calendar using push notifications api. These channels have finite lifetime, so they expire and there is no way to renew it. I'm setting up a cron to start new channel in case old one had expired. Without knowledge of existing channels, only option is to start a channel every now and then to be sure that given resources are being watched.
Is it possible to check if a resource is being watched by an active channel in google calendar api?
GCal's API doesn't allow you to request that directly, so you'd have to handle that logic.
Here's an example of how you could keep your channels active:
When creating a channel, include the expiration time and persist that value somewhere
During your cron task, check if it's beyond the expiration time
If so, create a new channel
If not, check if it will expire before the next cron run
If so, renew it now
Optional: Delete the original watch channel (so no overlaps)
If not, continue
Another approach would be to use refresh tokens to ensure API access, without having to worry about whether a channel is expired or not.
Google Calendar API overview
Node client example
I'm trying to write a Zap which will get events from a Google Calendar for the current week and generate a string describing when I'm in the office "Mon, Wed, Fri". The only thing that's stumping me is step 0. How do I auth for the Google Calendar API in a Zapier Code block? It appears that Google calendar only supports Oauth2.0 and only gives out short-lived tokens but I want this scheduled job to only run in the background and have no recourse for user-interaction. Is there any way to generate a long-lived access token?
David here, from the Zapier Platform team.
Unfortunately, that's not something that's easily doable. Part of what Zapier does for you is refresh the tokens and ensure everything is running as expected. Searching for a bunch of data (this week's events) isn't really in the zapier wheelhouse at this time.
If you're only concerned with new events, you could set up a "new event" trigger that adds the date of the event into Storage and a second zap that runs weekly, reads storage, figures out what dates are busy, and generates your string. That wouldn't hold up for recurring events though, so it may not be what you're looking for.
Alternatively, you could try and do the refresh loop in your code block, but that's really tough. You'd need somewhere to persist the token (while Storage technically works, it's open to the public so it's not a great place to store creds, even if they are hard to find). You could try your request, refresh if the token is stale, then send the eventually present value onto a later step.
Sorry I don't have better news. Let me know if you've got any other questions!
I have an asp.net application
The User can purchase items and/or upgrade an existing service. I use PayPal to handle payments.
When the order is placed I put the order details into a table as a record. I also do this so that if the User revisits my page I check a flag in that table to tell them whether payment has been received.
If not received then I display a message on my web page telling them they have requested and upgrade and that we are currently waiting for payment.
I also disable any future purchases for that User so that they do not upgrade twice (or more).
Now, it occurs to me that when the User is redirected to PayPal to make that purchase that a payment could fail or they could close the web page.
In that case then the User would not be able to ever upgrade unless they send me a support email.
What is best practice? Wait for a time period to elapse and if no payment then allow User to try again or stick with the support email route?
If it is best to wait for a period of time what is an acceptable period of time to wait for?
I am using the post back of Form values method to imitate payment to PayPal:
https://www.paypal.com/cgi-bin/webscr
What you can do.
Each time the user is ready to leave your page and move to paypal to pay, you create
Clone of their order
A new unique ID connected with that cloned order.
So, if one order if fail, at any time the user come back is find a new set "Order"+"Unique ID". With that 2 elements can make a final payment.
You may end up with two or tree or more cloned orders, but from my experience because this is what I really do, is rare, and its safe for your customer and you.
Also please note that paypal is accept only one unique id for each order. If one ID is fail, then you must create a new one anyway.
The unique id you send to paypal go to the invoice parametre.
The manual for all parametres is this pdf - PayPal Payments Standard Intergration Guide... and there are more pdf for paypal...