Paypal webhook verification fails everytime - spring-boot

I have successfully implemented webhook integration using Sandbox in Paypal.
Now I want to make it more secure so that only Paypal signed notification is accepted.
I was trying to verify webhook signature using
https://developer.paypal.com/docs/api/webhooks/v1/#verify-webhook-signature_post
But it always returns FAILURE.
The request is :
{"auth_algo":"SHA256withRSA","transmission_time":"2020-08-17T12:11:08Z","cert_url":"https://api.sandbox.paypal.com/v1/notifications/certs/CERT-360caa42-fca2a594-1d93a270","webhook_id":"0JD18557VD498931R","transmission_id":"bbaae190-e082-11ea-aa52-1fdbf2bc8461","webhook_event":{"summary":"Payment completed for $ 5.0 USD","event_type":"PAYMENT.SALE.COMPLETED","create_time":"2020-08-17T12:11:05.015Z","resource":{"billing_agreement_id":"I-DNVD3H9UWYHL","amount":{"total":"5.00","currency":"USD","details":{"subtotal":"5.00"}},"payment_mode":"INSTANT_TRANSFER","update_time":"2020-08-17T12:10:39Z","create_time":"2020-08-17T12:10:39Z","protection_eligibility_type":"ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE","transaction_fee":{"currency":"USD","value":"0.45"},"protection_eligibility":"ELIGIBLE","links":[{"method":"GET","rel":"self","href":"https://api.sandbox.paypal.com/v1/payments/sale/8TV124151P468690Y"},{"method":"POST","rel":"refund","href":"https://api.sandbox.paypal.com/v1/payments/sale/8TV124151P468690Y/refund"}],"id":"8TV124151P468690Y","state":"completed","invoice_number":""},"resource_type":"sale","links":[{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-2XT265922L1486124-74F09092JL7840709","rel":"self","targetSchema":null,"method":"GET","enctype":null,"schema":null},{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-2XT265922L1486124-74F09092JL7840709/resend","rel":"resend","targetSchema":null,"method":"POST","enctype":null,"schema":null}],"id":"WH-2XT265922L1486124-74F09092JL7840709"},"transmission_sig":"RYILWohVPkK0hUrMjTSU3+fCgd6NTgqkjrZtsOJiC7FR3U3atOK1k29/Md8DQRReAicdfOpXrS7E4vrvB17HOM39w/D3i4Ohy34HL3CqSsZovL69lhfFmviCGkXjjSbkBhpKGJvQAB4q0E9AWl/SBZc4MUNGezIbk/laJZ6ikQuwGeEHCFaPVrza7kSlZRo03lM9sYSb7q3ixewYmz8voKIyJ2RYjOgsAohNFWgovtKwG+ac66YCp3ZRJLe4fL2Q1UaEDn5BnUhK+5Q2+EqD+BixpqNTuSmYqRwkyDTdrH1EPV5DRU4uYM0gJLXnBovGaqHe8JujpVs+dJu4Mrmgdg=="}
and the result is
{"verification_status":"FAILURE"}
Can someone help, please !!

If you are getting those values from the webhook simulator as they said in their documentation you can't verify mock webhooks.
What I used to do when I need to validate the value because some test need that part it's that using the smart buttons to generate the request and then catch the response in some webhook.

Verify Paypal webhook notification
How to verify the authenticity of the notification in php:
// get request headers
$headers = apache_request_headers();
// get http payload
$body = file_get_contents( 'php://input' );
// compose signature string: The third part is the ID of the webhook ITSELF(!),
// NOT the ID of the webhook event sent. You find the ID of the webhook
// in Paypal's developer backend where you have created the webhook
$data =
$headers['Paypal-Transmission-Id'] . '|' .
$headers['Paypal-Transmission-Time'] . '|' .
'<WEBHOOK ID FROM THE DEVELOPER DASHBOARD>' . '|' . crc32( $body );
// load certificate and extract public key
$pubKey = openssl_pkey_get_public( file_get_contents( $headers['Paypal-Cert-Url'] ) );
$key = openssl_pkey_get_details( $pubKey )['key'];
// verify data against provided signature
$result = openssl_verify(
$data,
base64_decode( $headers['Paypal-Transmission-Sig'] ),
$key, 'sha256WithRSAEncryption'
);
if ( $result == 1 ) {
// webhook notification is verified
} elseif ( $result == 0 ) {
// webhook notification is NOT verified
} else {
// there was an error verifying this
};
The transmission id, the transmission date, the webhook id and a CRC over the HTTP body. The first two can be found in the header of the request, the webhook id in the developer backend (of course, that id will never change), the CRC is calculated like shown below.
The certificate's location is in the header, too, so we load it and extract the private key.
Last thing to watch out for: The name of the algorithm provided by Paypal (again in a header field) is not exactly the same as understood by PHP. Paypal calls it "sha256WithRSA" but openssl_verify will expect "sha256WithRSAEncryption".

Related

How to get payer email after a success payment wit paypal API (Laravel paypal/rest-api-sdk-php)?

I'm using the paypal API with Laravel, everything works fine. I just need to find the way to get the user email address and name.
$payment = Payment::get($payment_id, $this->_api_context);
$execution = new PaymentExecution();
$execution->setPayerId( $request->query('PayerID') );
$result = $payment->execute($execution, $this->_api_context);
if ($result->getState() == 'approved') {
// I should get the info about the payer here
}
The v1/payments PayPal-PHP-SDK is deprecated, you should not use it for any new integration.
The current supported SDK is the v2/checkout/orders Checkout-PHP-SDK, which you should use instead.
You can try the srmklive package "v3" if you like, otherwise integrate directly.
You should be able to create two routes, one for 'Create Order' and one for 'Capture Order', documented here. These routes should return only JSON data (no HTML or text). Pair those two routes with the following approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server
The capture order response may have details about the payer and payment, if not use can do a 'Get' API call on the order ID for this information. Store it in your database before returning the JSON to the client code.

Twilio Send reply not hitting provided URL

I have configured Twilio phone number and given settings as per the documentation. On the callback URL i have written code like this
Route::post('/replyMessages', function(){
Log::info('inside reply messages URL');
$response = new \Twilio\TwiML\MessagingResponse();
Log::info(print_r($response, true));
$message = $response->message("Testing sms from twilio to staffing backbone");
print_r($response);
Log::info('SMS reply URL callback');
});
When user is replied from their mobile nothing is happening. If URL hits the log, then it will print the text given but not working. Kindly help me on this.
As per one of the document in Twilio they are expected to install ngrok is that necessary in order to call our URL? documentation link
ngrok is not explicitly required, it's just recommended as an easy way to make your local development environment accessible from outside of your network.
If the code is running on your local development machine, ensure that you able to query that endpoint from a device outside of your network (eg, from a mobile phone with wifi turned off). If not, ngrok will help with this.
If the code has been deployed to a server that is already publicly accessible, query it and ensure there are no errors being output.
Original answer (related to code snippet):
Try switching out print_r($response); with echo $response;.
print_r will be outputting the structure of the MessagingResponse object, whereas echo will cast your $response to a string and output the XML payload that Twilio is expecting.
Your current payload probably looks something like this:
Twilio\TwiML\MessagingResponse Object
(
[name:protected] => Response
[attributes:protected] => Array
(
)
[children:protected] => Array
(
[0] => Twilio\TwiML\Messaging\Message Object
(
[name:protected] => Message
[attributes:protected] => Array
(
)
[children:protected] => Array
(
[0] => Testing sms from twilio to staffing backbone
)
)
)
)
=> true
But Twilio will be expecting an XML payload as below:
<?xml version="1.0" encoding="UTF-8"?>
<Response><Message>Testing sms from twilio to staffing backbone</Message></Response>
Since it's a POST request, I'm guessing the VerifyCsrfToken middleware is stopping this request.
Add 'replyMessages' to the $except array in app/Http/Middleware/VerifyCsrfToken.php file, so it should end up like this:
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
*
* #var bool
*/
protected $addHttpCookie = true;
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'replyMessages'
];
}
As per checking my configuration is correct and there is some problem in the Twilio.
The initial mobile which we have tried for sending replies and it is recording log and everything works but it is not triggered the callback URL. Later we have tried to reply from new mobile number it worked. Nothing changed in the configuration. So following up with Twilio on the resolution.
Thanks all for the help.

Resend 2Factor Authentication verification code (Laravel with Nexmo)

I have a laravel app with integrated with 2 factor authentication using Nexmo API . I have manage to verify the code in the login process . so now i need to re-send the verification again ..
I already followed this article and called same method again but it's not working
https://www.nexmo.com/blog/2017/07/26/2fa-logins-laravel-nexmo-dr/
Here is Re-Send Button
Re-Verify
Here is the route
Route::get('/reverify', 'Auth\LoginController#reauthenticated')->name('reverify');
Here is the Re-Send Code
public function reauthenticated(Authenticatable $user)
{
$verification = Nexmo::verify()->start([
'number' => $user->phone_number,
'brand' => 'Ezy verification'
]);
$request->session()->put('verify:request_id', $verification->getRequestId());
return redirect('verify');
}
So I just need to know how can I resend the verification code

Google MyBusiness access from webservers without redirects

As detailed in my question to the the Google API team, I would like to work out a way to avoid redirects.
In theory this should be possible as an Authentication Code from one client (JavaScript) should be agnostic of the client and thus it should work if passed to the PHP client to fetch the access and refresh tokens.
Steps in theory:
Client gets an authorization code
Client exchanges the authorization code for the access and refresh tokens
How am I attempting this?
Run the JavaScript client to get the Authentication token
GoogleAuth = gapi.auth2.getAuthInstance()
GoogleAuth.grantOfflineAccess({
scope: 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.business.manage https://www.googleapis.com/auth/plus.me openid email profile'
}).then(function (resp) {
var auth_code = resp.code;
console.log("AuthCode:" + auth_code)
})
At this point i get a code, not sure if this is an authorization code or access token but i cannot see any other function in the Javascript library that is Authorization token explicit/specific.
Use the Authentication Token in the PHP API Library
$error = $request->get('error');
$code = $request->get('code');
if($error){
throw new Exception('Error from authenticating ' . $error);
}
$client = new \Google_Client();
$client->setAuthConfig(getcwd() . '/../client_secret.apps.googleusercontent.com.json');
$client->setAccessType("offline"); // offline access
$client->setIncludeGrantedScopes(true); // incremental auth
$client->addScope(
array(
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/plus.business.manage'
)
);
$client->setRedirectUri('http://myserver.com/code');
$client->setApprovalPrompt('force');
$client->fetchAccessTokenWithAuthCode($code);
$accessToken = $client->getAccessToken();
return new Response(
"<html><body>Authenticated with code : " . $code . "<br/>\n\n".
" Access Token is : ". var_export($accessToken, true) . "</body></html>"
);
Access token is still null. the line $accessToken = $client->getAccessToken(); returns null or false.
This works in the full PHP based version but the PHP version is based on creating a link that the user needs to follow, the user is then on the Google server and can approve the app, when approved the user is redirected back to the app. Then the app receives the Authentication code.
I just would like to avoid redirects due to the architecture of single pages apps or just preference. The only alternative I can think of is to open a popup and notify the original window when access and refresh codes are returned so that the PHP client can go on querying the API, but it is an ugly solution IMHO.
Is there another way to get an authorization code that works on PHP but obtained from JavaScript?

Getting access token issues using PHP client libraries

I am trying to integrate several Google API calls into a custom Drupal 8 module.
I am basically trying to first get my custom class to get an access token from Google via OAuth before I try do anything else. I am doing this by using a class function with everything simply in one place. The function is as follows:
public function testTokenRequest(): void
{
// Setup Google Client Config within context of initialized class
$this->googleClient->setClientId($this->googleClientID);
$this->googleClient->setClientSecret($this->googleClientSecret);
$this->googleClient->setDeveloperKey($this->googleApiKey);
// Add Google MyBusiness scope
$this->googleClient->setScopes(array('https://www.googleapis.com/auth/plus.business.manage'));
try {
$accessToken = $this->googleClient->getAccessToken(); // null returned where breakpoint hit
$this->googleAccessToken = $accessToken; // Put xdebug breakpoint here
} catch (Exception $exception) {
echo $exception->getMessage();
}
}
Currently all I get is a null returned for the $accessToken = $this->googleClient->getAccessToken(); call.
Unsure where I am going wrong, possibly the AddScopes call because the vendor documentation for the apiclient does this slightly differently, i.e. $client->addScope(Google_Service_Plus::PLUS_ME); but I couldn't find the correct class to use for the MyBusinessAPI scope so used the OAuth playground string instead https://www.googleapis.com/auth/plus.business.manage
I get an OAuth Playground AccessToken returned when I use that but end up with a Permission Denied error instead even though I have the GMB API added to my whitelist under credentials.
Google MyBusiness is Oauth2 based. The access token is not received until the user has approved your app, it is normal that the function returns null if the user has not approved the app yet.
Here is an example on how you create a link where to send the user to start authentication and authorization for your app.
$client = new \Google_Client();
$client->setAuthConfig(getcwd() . '/../client_secret.apps.googleusercontent.com.json');
$client->setAccessType("offline"); // offline access
$client->setIncludeGrantedScopes(true); // incremental auth
$client->addScope(
array(
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/plus.business.manage'
)
);
$client->setRedirectUri('http://server.com/code');
$client->setApprovalPrompt('force');
return new Response(
'<html><body>Authenticate here : <a href="' .
$auth_url = filter_var($client->createAuthUrl(), FILTER_SANITIZE_URL)
. '">HERE</a></body></html>'
);
The example above assumes your server will also implement a /code endpoint where the user is redirected to with the authorization token, then you need to call the api to exchange the token with the access and refresh code.
This document will help you understand further
https://developers.google.com/api-client-library/php/auth/web-app

Resources