stripe webhook account.updated is not working - php

I am trying to send an (simple test) email when I receive an account.updated call for a custom account from Stripe API. My other webhooks to create charges and inform customers about successful or failed charges work like this, but here I get an error 500 (I can see that in the dashboard of the custom account) and the mail is NOT send, so I am not sure what I am doing wrong here. My code looks like this:
<?php
require_once('vendor/autoload.php');
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey("sk_test_XXXX");
// Retrieve the request's body and parse it as JSON
$input = #file_get_contents("php://input");
$event_json = json_decode($input);
// Verify the event by fetching it from Stripe
$event = \Stripe\Event::retrieve($event_json->id);
// Do something with $event
if ($event->type == 'account.updated') {
// The E-Mail message to send to inform about a succeeded charge
$message = 'test';
// Send the E-Mail
mail('test#example.com', 'We need more info about you!', $message);
}
http_response_code(200); // PHP 5.4 or greater
Thank you for your help!

If you look at your web server's error.log (usually accessible from your hosting control panel or in /var/log/) do you see more detail on what's causing the 500?
Could it be $event = \Stripe\Event::retrieve($event_json->id); failing?
If the event is occurring directly on a connected account, you may also need to pass the account id to retrieve it.
See here for a bit more context,
https://stripe.com/docs/connect/webhooks
The code would be more like:
$event = \Stripe\Event::retrieve(
array("id" => $event_json->id),
array("stripe_account" => $event_json->account));
https://stripe.com/docs/connect/authentication#authentication-via-the-stripe-account-header

Related

Google pub/sub subscription data doesn't match with the app

I'm trying to listen for subscription changes (new and existing) of my Google Play app on the server. Here's the code I'm using. This uses the google/cloud-pubsub composer package:
$projectId = 'app-name';
$keyFile = file_get_contents(storage_path('app/app-name.json'));
$pubsub = new PubSubClient([
'projectId' => $projectId,
'keyFile' => json_decode($keyFile, true)
]);
$httpPostRequestBody = file_get_contents('php://input');
$requestData = json_decode($httpPostRequestBody, true);
info(json_encode($requestData));
$message = $pubsub->consume($requestData);
info(json_encode($message));
The code above works but the problem is that the data I get doesn't match the one I'm getting in the app side. This is a sample data:
{
"message":{
"data":"eyJ2ZXJ...",
"messageId":"16797998xxxxxxxxx",
"message_id":"1679799xxxxxxxxx",
"publishTime":"2020-12-15T02:09:23.27Z",
"publish_time":"2020-12-15T02:09:23.27Z"
},
"subscription":"projects\/app-name\/subscriptions\/test-subs"
}
If you base64_decode() the data, you'll get something like this:
{
version: "1.0",
packageName: "com.dev.app",
eventTimeMillis: "1607997631636",
subscriptionNotification: {
version: "1.0",
notificationType: 4,
purchaseToken: "kmloa....",
subscriptionId: "app_subs1"
}
}
This is where I'm expecting the purchaseToken to be the same as the one I'm getting from the client side.
Here's the code in the client-side. I'm using Expo in-app purchases to implement subscriptions:
setPurchaseListener(async ({ responseCode, results, errorCode }) => {
if (responseCode === IAPResponseCode.OK) {
const { orderId, purchaseToken, acknowledged } = results[0];
if (!acknowledged) {
await instance.post("/subscribe", {
order_id: orderId,
order_token: purchaseToken,
data: JSON.stringify(results[0]),
});
finishTransactionAsync(results[0], true);
alert(
"You're now subscribed! You can now use the full functionality of the app."
);
}
}
});
I'm expecting the purchaseToken I'm extracting from results[0] to be the same as the one the Google server is returning when it pushes the notification to the endpoint. But it doesn't.
Update
I think my main problem is that I'm assumming all the data I need will be coming from Google Pay, so I'm just relying on the data published by Google when a user subscribes in the app.
This isn't actually the one that publishes the message:
await instance.post("/subscribe")
It just updates the database with the purchase token. I can just use this to subscribe the user but there's no guarantee that the request is legitimate. Someone can just construct the necessary credentials based on an existing user and they can pretty much subscribe without paying anything. Plus this method can't be used to keep the user subscribed. So the data really has to come from Google.
Based on the answer below, I now realized that you're supposed to trigger the publish from your own server? and then you listen for that? So when I call this from the client:
await instance.post("/subscribe", {
purchaseToken
});
I actually need to publish the message containing the purchase token like so:
$pubsub = new PubSubClient([
'projectId' => $projectId,
]);
$topic = $pubsub->topic($topicName);
$message = [
'purchaseToken' => request('purchaseToken')
];
$topic->publish(['data' => $message]);
Is that what you're saying? But the only problem with this approach is how to validate if the purchase token is legitimate, and how to renew the subscription in the server? I have a field that needs to be updated each month so the user stays "subscribed" in the eyes of the server.
Maybe, I'm just overcomplicating things by using pub/sub. If there's actually an API which I could pull out data from regularly (using cron) which allows me to keep the user subscription data updated then that will also be acceptable as an answer.
First of all - I have a really bad experience with php and pubsub because of the php PubSubClient. If your script is only waiting for push and checking the messages then remove the pubsub package and handle it with few lines of code.
Example:
$message = file_get_contents('php://input');
$message = json_decode($message, true);
if (is_array($message)) {
$message = (isset($message['message']) && isset($message['message']['data'])) ? base64_decode($message['message']['data']) : false;
if (is_string($message)) {
$message = json_decode($message, true);
if (is_array($message)) {
$type = (isset($message['type'])) ? $message['type'] : null;
$data = (isset($message['data'])) ? $message['data'] : [];
}
}
}
I'm not sure how everything works on your side but if this part publishes the message:
await instance.post("/subscribe", {
order_id: orderId,
order_token: purchaseToken,
data: JSON.stringify(results[0]),
});
It looks like it's a proxy method to publish your messages. Because payload sent with it is not like a PubSub described schema and in the final message it doesn't look like IAPQueryResponse
If I was in your situation I will check few things to debug the problem:
How I publish/read a message to/from PubSub (topic, subscription and message payload)
I will write the publish mechanism as it is described in Google PubSub publish documentation
I will check my project, topic and subscription
If everything is set-up correctly then I will compare all other message data
If the problem persist then I will try to publish to PubSub minimal amount of data - just purchaseToken at the start to check what breaks the messages
For easier debug:
Create pull subscription
When you publish a message check pull subscription messages with "View messages"
For me the problem is not directly in PubSub but in your implementation of publish/receiving of messages.
UPDATE 21-12-2020:
Flow:
Customer create/renew subscription
Publish to pubsub with authentication
PubSub transfers the message to analysis application via "push" to make your analysis.
If you need information like:
New subscribers count
Renews count
Active subscriptions count
You can create your own analysis application but if you need something more complicated then you have to pick a tool to met your needs.
You can get the messages from pubsub also with "pull" but there are few cases I've met:
Last time I've used pull pubsub returns random amount of messages - if my limit is 50 and I have more than 50 messages in the queue I'm expecting to get 50 messages but sometimes pubsub gives me less messages.
PubSub returned messages in random order - now there is an option to use ordering key but it's something new.
To implement "pull" you have to run crons or something with "push" you receive the message as soon as possible.
With "pull" you have to depend on library/package (or whatever in any language it's called) but on "push" you can handle the message with just few lines of code as my php exapmle.

PHP verify Paypal webhook signature

I'm having trouble trying to verify paypal webhook signatures with PHP. Using the new V2 of paypals APIs I am receiving the paypal webhook on my page.
However I can not seem to successfully validate the signature.
From the link HERE I got some sample webhook validation PHP code from paypal.
I can not get it working, I don't know where I am supposed to get bootstrap.php from in the paypal code. The paypal information seems incomplete or half baked. Paypal seems to be terrible to set-up compared to Stripe.
Has anyone got experience of validating paypal webhook signatures with PHP when using V2 of the paypal APIs ?
Well I have come to the conclusion that the Paypal developer information is rather poor, it is scatted all over the place, on multiple different pages and sites. The examples they give on the paypal developer website HERE are not a complete picture of what is required to validate a webhook signature. Stripe developer documentation is much better formatted and concise.
Installing Paypal Checkout V2 SDK does not give you the necessary development tools to validate paypal webhook signatures i.e. you can process payments and receive webhooks but you can not validate the webhook signatures....I know stupid. Tip do not download the SDK directly as you will not include the required autoload.php file. Use composer to install Paypal Checkout V2 SDK so that you get the autoload.php file.
Once you are to a point that you can process payments and receive webhooks form paypal you need to install the another SKD called Paypal Rest API SDK. Again use composer to install the SDK so that you get a autoload.php file which you will need.
When you install Paypal Rest API SDK amazingly you will still be missing files that are required to validate the payapl webhook signatures. I can find no mention of these anywhere on the paypal developer website.
bootstrap.php & common.php
Thanks to #Grumpy I got some samples provided on github HERE
Note you will probably need to modify the samples a bit in order to get them working with your website. Tip set the logger to false and save yourself some bother if you don't have the necessary access permissions to write.
Once you have bootstrap.php & common.php files created you can write the code for your webhook endpoint page i.e. the page that paypal sends the webhook to. I have included my PHP code below for how to validate and then process the paypal webhook. Tip in the below code you need to specify the webhook ID, each webhook that you create in paypal has a unique ID. Also when you are testing you can not use webhook simulator as this will fail validation, you can make manually a payment with your sandbox account details which will trigger a webhook payment event.
Paypal sure don't make it easy, their documentation is all over the place compared to Stripe. The Paypal webhooks can sometimes take several minutes to arrive after a payment is made, very frustrating when trying to debug. Also it's a bit ridiculous that they have a webhook simulator on the paypal developer website that can not be used to validate signatures...if stripe can do it why can't paypal.
<?php
//get the webhook payload
$requestBody = file_get_contents('php://input');
//check if webhook payload has data
if($requestBody) {
//request body is set
} else {
//request body is not set
exit();
}
use \PayPal\Api\VerifyWebhookSignature;
use \PayPal\Api\WebhookEvent;
$apiContext = require __DIR__ . '/bootstrap.php';
//Receive HTTP headers that you received from PayPal webhook.
$headers = getallheaders();
//need header keys to be UPPERCASE
$headers = array_change_key_case($headers, CASE_UPPER);
/*
example header paypal signature content for webhook, these values are recieved as an array, we then need to use this data to verify the payload
CONTENT-LENGTH : 1376
CORRELATION-ID : 6db85170269e7
USER-AGENT : PayPal/AUHD-214.0-54377828
CONTENT-TYPE: application/json
PAYPAL-AUTH-ALGO : SHA256withRSA
PAYPAL-CERT-URL : https://api.paypal.com/v1/notifications/certs/CERT-360caa42-fca2a784-5edc0ebc
PAYPAL-AUTH-VERSION : v2
PAYPAL-TRANSMISSION-SIG : Hc2lsDedYdSjOM4/t3T/ioAVQqFPNVB/AY/EyPNlavXk5WYUfnAmt9dyEP6neAPOjFHiVkXMK+JlLODbr6dalw6i26aFQdsPXqGl38Mafuu9elPE74qgsqNferUFgHi9QFXL+UZCNYcb4mvlDePXZIIAPbB0gOuFGOdEv2uqNwTCSAa/D8aguv1/51FWb3RkytFuVwXK/XNfIEy2oJCpDs8dgtYAZeojH8qO6IAwchdSpttMods5YfNBzT7oCoxO80hncVorBtjj1zQrkoynEB9WNNN9ytepNCkT8l29fQ4Sx/WRndm/PESCqxqmRoYJoiSosxYU3bZP7QTtILDykQ==
PAYPAL-TRANSMISSION-TIME : 2020-04-05T14:40:43Z
PAYPAL-TRANSMISSION-ID : 6dec99b0-774b-11ea-b306-c3ed128f0c4b
*/
//if any of the relevant paypal signature headers are not set exit()
if(
(!array_key_exists('PAYPAL-AUTH-ALGO', $headers)) ||
(!array_key_exists('PAYPAL-TRANSMISSION-ID', $headers)) ||
(!array_key_exists('PAYPAL-CERT-URL', $headers)) ||
(!array_key_exists('PAYPAL-TRANSMISSION-SIG', $headers)) ||
(!array_key_exists('PAYPAL-TRANSMISSION-TIME', $headers))
)
{
exit();
}
//specify the ID for the webhook that you have set up on the paypal developer website, each web hook that you create has a unique ID
$webhookID = "ENTER_YOUR_WEBHOOK_ID_HERE";
//start paypal webhook signature validation
$signatureVerification = new VerifyWebhookSignature();
$signatureVerification->setAuthAlgo($headers['PAYPAL-AUTH-ALGO']);
$signatureVerification->setTransmissionId($headers['PAYPAL-TRANSMISSION-ID']);
$signatureVerification->setCertUrl($headers['PAYPAL-CERT-URL']);
$signatureVerification->setWebhookId($webhookID);
$signatureVerification->setTransmissionSig($headers['PAYPAL-TRANSMISSION-SIG']);
$signatureVerification->setTransmissionTime($headers['PAYPAL-TRANSMISSION-TIME']);
$signatureVerification->setRequestBody($requestBody);
$request = clone $signatureVerification;
try {
$output = $signatureVerification->post($apiContext);
} catch (Exception $ex) {
//error during signature validation, capture error and exit
ResultPrinter::printError("Validate Received Webhook Event", "WebhookEvent", null, $request->toJSON(), $ex);
exit(1);
}
$sigVerificationResult = $output->getVerificationStatus();
// $sigVerificationResult is a string and will either be "SUCCESS" or "FAILURE"
//if not webhook signature failed validation exit
if($sigVerificationResult != "SUCCESS"){
exit();
}
else if($sigVerificationResult == "SUCCESS"){
//paypay webhook signature is valid
//proceed to process webhook payload
//decode raw request body
$requestBodyDecode = json_decode($requestBody);
//pull whatever info required from decoded request body, some examples below
$paymentSystemID = $requestBodyDecode->id;
$eventType = $requestBodyDecode->event_type;
//do something with info captured from the webhook payload
}
This seems to work with php 8.1:
if (openssl_verify(
data: implode(separator: '|', array: [
$httpPayPalTransmissionId,
$httpPayPalTransmissionTime,
$webhookID,
crc32(string: $rawRequestBody),
]),
signature: base64_decode(string: $httpPayPalTransmissionSignature),
public_key: openssl_pkey_get_public(public_key: file_get_contents(filename: $cachedHttpPayPalCertUrl)),
algorithm: 'sha256WithRSAEncryption'
) === 1) {
die('OK');
} else {
die('FAILED');
}

Sendgrid : get bounce status code with webhooks (php api)

I use Sendgrid to send emails. Emails are sent using their web API and I use an endpoint to get each email events.
Here is the code of this endpoint (that Sendgrid call each time when an event occur, after sending an email).
I would like to get the status code of each event (eg : 5.1.1 for a bounce).
Here is what I tried so far :
$data = file_get_contents("php://input");
$events = json_decode($data, true);
foreach ($events as $event) {
$sg_message_id = $event['sg_message_id']; //OKAY
$event = $event['event']; //OKAY (eg : "bounce")
$status = $event['status']; //NOT OKAY ("undefined index")
//and if I try :
$status = $event['event']['status']; //I get the first letter of the event (eg : "b" for bounce)
}
the documentation (here : https://sendgrid.com/docs/API_Reference/Webhooks/event.html, Bounce part) says there is a field "status" (eg : 5.1.1), and I don't understand why it doesn't work,
Any idea?
I understood the problem :
I tried to simulate a bounce error using a fake user email like
user#gmail1.com
but Sendgrid didn't process the email (host not reacheable) and returned a "dropped" event.
Instead, if I send an email to:
another-user-unknow#gmail.com
then sendgrid return a process event and then a bounce event, with the bounce status code:)

Gmail API Request Excel VBA PHP

I'm using Excel VBA to get a user's oAuth2 token for the mail.google.com scope for a native application. It works fine so I have the the user's Access Token (it refreshes if it expires) and I have the user's email address. I was using Zend SMTP to send the emails but found out there are limits to bulk sending emails this way (currently my account is locked out due to going over quota while trying to figure this out over the weekend). My client may send 2,000 emails at once and locking themselves out of their Gmail account for 24 hours is unacceptable. I think using the Gmail REST API will work, as it has higher quotas, but not finding any detailed PHP examples of how to create and send an HTML email via an HTTP request.
I know I have to create the MIME email and think I can figure that out (using this thread stackoverflow.com/questions/24940984/send-email-using-gmail-api-and-google-api-php-client) or from the Zend emails I was successfully creating. My question is what PHP code do I use to create and send the email?
The Google Dev examples all reference /userId/ (from developers.google.com/gmail/api/v1/reference/) but not sure where to get that if I just have the users token and their email address.
I assume it's something like this:
<?php
require 'GoogleAPI/src/Google/autoload.php';
extract($_POST); // use to get my client token, client's email address, the email to, cc, bcc, subject, body, etc.
//<Build the email $mime message here>
$m = new Google_Service_Gmail_Message();
$data = base64_encode($mime);
$data = str_replace(array('+','/','='),array('-','_',''),$data); // url safe
$m->setRaw($data);
$service->users_messages->send('me', $m);
?>
I don't know where to put the user's token. I know 'me' is the person that is authenticated.
I'm open to using an HTTP request as well, something like https://mywebsite.com/sendgmail.php?token=[UsersToken]&UserEmail=joe#test.com&ToEmail=toperson#xyz.com&Subject=HI&Body=Thanks for your help instead of the post method.
Just not sure how to implement that either.
I'm self taught PHP and VBA and new to the google api world so please forgive me if this is easy. I'm also looking into boxspring but trying to use the native APIs and PHP first.
EDIT:
So I've tried this using the examples but getting an error that my token is not in proper JSON format. I just want to send the token as a string. If I can't is there a way to make the string into JSON so oauth2 will accept it?:
<?php
require 'GoogleAPI/src/Google/autoload.php';
/**
* Returns an authorized API client.
* #return Google_Client the authorized client object
*/
$client = new Google_Client();
$client->setAccessToken('ya29.hwFcwk2M73vaPwNObeuwizHGjXT2y6UsAFEcDIvRAoWTM28gu2pJeK4GiMySkfAllTOQvXVMYfffff');
// Get the API client and construct the service object.
//$client = getClient();
$service = new Google_Service_Gmail($client);
// Print the labels in the user's account.
$user = 'me';
$results = $service->users_labels->listUsersLabels($user);
if (count($results->getLabels()) == 0) {
print "No labels found.\n";
} else {
print "Labels:\n";
foreach ($results->getLabels() as $label) {
printf("- %s\n", $label->getName());
}
}
?>
Error:
Fatal error: Uncaught exception 'Google_Auth_Exception' with message
'Could not json decode the token' in
/home/[my domain]/public/GoogleAPI/src/Google/Auth/OAuth2.php:179
Stack trace: #0
/home/[my domain]/public/GoogleAPI/src/Google/Client.php(215):
Google_Auth_OAuth2->setAccessToken('ya29.hwFcwk2M73...') #1
/home/[my domain]/public/labels.php(17):
Google_Client->setAccessToken('ya29.hwFcwk2M73...') #2 {main} thrown
in
/home/[my domain]/public/GoogleAPI/src/Google/Auth/OAuth2.php
on line 179
I'm sure I'm making this more difficult than it should be. I'm using the PHP example of showing a users labels, even though my final goal is to send emails.

SendGrid Web API bounced email header response

I am sending emails via SendGrid with this inside the x-smtpapi header
$json_string = array(
'unique_args' => array (
'email_id' => 1
)
);
It all seems to send okay, inside SendGrid I can view the "email_id" in the Email activity under Unique Args.
However when I try to use the API to look at this email, I cannot find a way to actually get these unique arguments from the API.
I am using this to try and get the headers returned with the bounced emails.
$request = 'https://api.sendgrid.com/api/bounces.get.json&api_user=username&api_key=password'
All I get is just the email addresses that have bounced, not the header information (the unique arguments)
I want to know, is it possible to get the unique arguments from the API. I have read it multiple times to no avail.
I hope this makes sense.
Thanks
At present there's no way request to lookup specific events by unique_arg with the Web API.
However, the SendGrid Event Webhook will give you granular data on each event, such as a bounce as it happens. The Event Webhook POSTs data to your server every time an action is taken upon an email (e.g. open, click, bounce).
Once you receive it, you're responsible for storing this, although it's not a typical API it gives very specific data on events which you can then compile and rehash however you like.
To get started using the webhook, you'll do something like the following, and have SendGrid POST to the following script:
<?php
$data = file_get_contents("php://input");
$events = json_decode($data, true);
foreach ($events as $event) {
// Here, you now have each event and can process them how you like
process_event($event);
}
[Taken from the SendGrid Webhook Code Example]

Categories