PHP webpush library (Minishlink\WebPush) no event.data - php

I have configured webpush and all is going well... I have registered the service-worker.js, can request permission, save the subscription to my db and send a push notification from the server using the library which is installed.
When I send the push notification
[
'subscription' => Subscription::create($subs_arr),
'payload' => '{"title":"test title","msg":"Hello World!", "icon":"https://www.newhomesforsale.co.uk/media/chevron.png","url":"https://www.newhomesforsale.co.uk/"}'
]
];
I get a success message:
[v] Message sent successfully for subscription https://fcm.googleapis.com/fcm/send/e2JHJ2YcIfM:APA91bHwU7CruFTDkpAH-zbnJRNhvJEK-mCze2hFNa48mdK8pk-oWuXJUn57Ai9Nw0d-skviCfJ40g1yX7qWKucGHPF3jeNyhkJfZ-8kpxYJNQowrAR561b0dQZJAseL_eBsJRMrxnDP.
and a push message appears in the browser - great.
The problem I have is that the service-worker file doesn't seem to see the payload information because the message displays even as simply
Oh No - no data {"isTrusted": true}
Service-worker file:
self.addEventListener('push', function(event) {
if (!(self.Notification && self.Notification.permission === 'granted')) {
return;
}
const sendNotification = body => {
// you could refresh a notification badge here with postMessage API
const title = "Web Push example";
return self.registration.showNotification(title, {
body,
});
};
if (event.data) {
const message = event.data.text();
event.waitUntil(sendNotification(message));
} else {
do_serverlog('Push event but no data');
sendNotification("Oh No - no data" + JSON.stringify(event));
}
});
Would be great to get the last piece of the puzzle to figure out how to correctly send/read the payload.

I have found that I made a mistake passing throught the keys into the subscription ($subs_arr).
It seems that if you have provide the endpoint, but not the keys, the push notification still works, but with the data stripped out. This sidetracked me as I had assumed that it wouldn't work at all if the keys were missing.
I thought maybe this might help someone at some point.

Related

Either you do not have a card saved to your Wallet or the current domain is not registered for Apple Pay

I'm trying to set up Apple Pay using Stripe on my website.
I'm following the instructions here https://stripe.com/docs/stripe-js/elements/payment-request-button
I've already registered my domain on Stripe, seen here
Then i added test card to my wallet on my macbook, seen here
When i run ngrok on my localhost, ngrok shows me this
ngrok (Ctrl+C to quit)
Hello World! https://ngrok.com/next-generation
Session Status online
Account XXXXXXX (Plan: Pro)
Version 3.0.6
Region United States (us)
Latency 302ms
Web Interface http://127.0.0.1:4040
Forwarding https://XXXXXXXXXX.ngrok.io -> https://blabla.xxxxx.com:443
Connections ttl opn rt1 rt5 p50 p90
91 0 0.01 0.01 5.03 9.41
HTTP Requests
-------------
GET /frontend/web/debug/default/toolbar 200 OK
GET /frontend/web/payment/checkout/ 200 OK
GET /frontend/web/debug/default/toolbar 200 OK
GET /frontend/web/payment/checkout/ 200 OK
GET /frontend/web/debug/default/toolbar 200 OK
GET /frontend/web/payment/checkout/ 200 OK
GET /frontend/web/debug/default/toolbar 200 OK
GET /frontend/web/payment/checkout/ 200 OK
GET /frontend/web/debug/default/toolbar 200 OK
GET /frontend/web/payment/checkout/ 200 OK
When i view my site through the ngrok domain on Safari, i get this error on the safari console
Either you do not have a card saved to your Wallet or the current domain (XXXXXXXX.ngrok.io) is not registered for Apple Pay. Visit https://dashboard.stripe.com/account/apple_pay to register this domain.
I'm using stripe php and stripe.js for my payment. Here is my JS
var publishableKey = document.getElementById('payment-form').getAttribute('data-stripe-publishable-key');
var paymentIntent = document.getElementById('payment-form').getAttribute('data-stripe-paymentIntent');
document.addEventListener('DOMContentLoaded', async () => {
if (!publishableKey) {
document.getElementById('applepay-error').textContent = 'No publishable key returned from the server';
}
// 1. Initialize Stripe
const stripe = Stripe(publishableKey, {
apiVersion: '2020-08-27',
});
// 2. Create a payment request object
var paymentRequest = stripe.paymentRequest({
country: 'US',
currency: 'usd',
total: {
label: 'Demo total',
amount: 1999,
},
requestPayerName: true,
requestPayerEmail: true,
});
// 3. Create a PaymentRequestButton element
const elements = stripe.elements();
const prButton = elements.create('paymentRequestButton', {
paymentRequest: paymentRequest,
});
// Check the availability of the Payment Request API,
// then mount the PaymentRequestButton
paymentRequest.canMakePayment().then(function (result) {
if (result) {
prButton.mount('#applepay-element');
} else {
document.getElementById('applepay-element').style.display = 'none';
document.getElementById('applepay-error').textContent = 'Apple Pay support not found. Check the pre-requisites above and ensure you are testing in a supported browser.';
}
});
paymentRequest.on('paymentmethod', async (e) => {
// Confirm the PaymentIntent without handling potential next actions (yet).
let {error, paymentIntent} = await stripe.confirmCardPayment(
clientSecret,
{
payment_method: e.paymentMethod.id,
},
{
handleActions: false,
}
);
if (error) {
document.getElementById('applepay-error').textContent = error.message;
// Report to the browser that the payment failed, prompting it to
// re-show the payment interface, or show an error message and close
// the payment interface.
e.complete('fail');
return;
}
// Report to the browser that the confirmation was successful, prompting
// it to close the browser payment method collection interface.
e.complete('success');
// Check if the PaymentIntent requires any actions and if so let Stripe.js
// handle the flow. If using an API version older than "2019-02-11" instead
// instead check for: `paymentIntent.status === "requires_source_action"`.
if (paymentIntent.status === 'requires_action') {
// Let Stripe.js handle the rest of the payment flow.
let {error, paymentIntent} = await stripe.confirmCardPayment(
clientSecret
);
if (error) {
// The payment failed -- ask your customer for a new payment method.
document.getElementById('applepay-error').textContent = error.message;
return;
}
document.getElementById('applepay-error').textContent = 'Payment ${paymentIntent.status}: ${paymentIntent.id}';
}
document.getElementById('applepay-error').textContent = 'Payment ${paymentIntent.status}: ${paymentIntent.id}';
});
});
Any idea how get this to work? Thank you.
There could be multiple reasons you want to double check:
Domain was set up in a different Stripe Account than the Publishable Key's owner account.
Issue with a specific Apple account.
If this involves Stripe Connect and Standard/Direct Charge it would be more complicated.
You can double check 1), ask your friend or colleague for help on checking 2). If that doesn't help, write to Stripe Support and they would be able to help checking further on 3).

Add 2FA or OTP (via e-mail) to Laravel Fortify 2FA via Authenticator App

I am using Laravel 8.x with Fortify and Jetstream/Livewire with 2FA / OTP turned on:
config/fortify.php
'features' => [
Features::registration(),
Features::resetPasswords(),
Features::emailVerification(),
Features::updateProfileInformation(),
Features::updatePasswords(),
Features::twoFactorAuthentication(),
],
I also customized that a bit with my route groups, etc. to allow some pages without authentication and other with. That works fine. However, many of our users are not really techies and find it to be annoying to use the Auth App (I sort of agree with that), although I like the baked in 2FA with Fortify.
What I would like to do is:
Modify the existing Fortify code to optionally allow for implementing 2FA via e-mail as well. It would probably be hard to implement one or the other, or both, so that a user could use whatever method is available, but seems like it would be possible to allow a user to pick which method they want. Built in Fortify method with an Auth App, or e-mail OTP with some other package or by modifying the Fortify code after publishing the files that are needed to modify.
I have looked around a little, and the one below seems pretty simple, but I would want to use that one for the e-mail method, and the Fortify one for the Authenticator method. That would be a user preference option.
github.com/seshac/otp-generator
I tried to do this by using the in-place Laravel 8.x JetStream Framework with Fortify by making some modifications as follows:
views/auth/login.blade.php
Changed the form to:
<form method="POST" action="{{ route('login') }}" id = "OTPlogin">
Added this after the form (I also have the Spatie Cookie reminder package).
<div id = "cookieremindertext">If not alreay done, click on "Allow Cookies". Read the Privacy Policy and Terms if you wish.</div>
<label for = "otp" id = "otpwrapper">Please Enter the OTP (One-Time Password) below and Click "LOGIN" again.
<input type="text" name="otp" id = "otp" value = ""/>
</label>
and then quite a bit of Javascript on the page as an addition:
function dynamicPostForm (path, params, target) {
method = "post";
var form = document.createElement("form");
form.setAttribute("method", method);
form.setAttribute("action", path);
if (target) {
form.setAttribute("target", "_blank");
}
for (const [key, value] of Object.entries(params)) {
if(params.hasOwnProperty(key)) {
var hiddenField = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", key);
hiddenField.value = value;
form.appendChild(hiddenField);
}
}
var hiddenField = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", "_token");
hiddenField.setAttribute("value", '{{ csrf_token() }}');
form.appendChild(hiddenField);
document.body.appendChild(form);
form.submit();
$(form).remove();
}
$("#OTPlogin").on("submit", function(e) {
e.preventDefault();
$.ajax({
type: "POST",
url: "/Auth/otp-generator",
dataType: "json",
data: {email:$("[name=email]").val(),password:$("[name=password]").val(),remember:$("[name=remember]").prop("checked"),otp:$("[name=otp]").val()},
context: $(this),
beforeSend: function(e) {$("body").addClass("loading");}
}).done(function(data, textStatus, jqXHR) {
if (data.error == 'otp') {
// $("#otp").val(data.otp);
$("#otpwrapper").show();
}
else if (data.message == "LOGIN") {
params = {};
params.email = $("[name=email]").val();
params.password = $("[name=password]").val();
params.remember = $("[name=remember]").prop("checked");
dynamicPostForm ('/login', params, false);
data.message = "Thank you . . . logging you in."
// This will fail on the backend if the session indicating a valid OTP did not get set.
}
showMessage("",data.message);
$(".modal-body").css("width", "320px");
});
});
That basically sort of bypasses the regular /login route initially until I get a response back from the backend indicating that the OTP has been verified via e-mail (maybe SMS later).
Added a new route in:
routes/web.php
// Receive User Name and Pass, and possibly otp from the login page.
Route::post('/Auth/otp-generator', [EmailController::class, 'sendOTP']); // move to middleware ?, Notifications or somewhere else ?
Created some new methods in my EmailController (should maybe refactor that to a different location or file, but as follows. That handles getting the OTP verified, and when it is sets: Session::put('OTP_PASS', true);
Http/Controllers/EmailController.php
protected function generateAndSendOTP($identifier) {
$otp = Otp::generate($identifier);
$expires = Otp::expiredAt($identifier)->expired_at;
try {
//$bcc = env('MAIL_BCC_EMAIL');
Mail::to($identifier)->bcc("pacs#medical.ky")->send(new OTPMail($otp->token));
echo '{"error":"otp","message":"Email Sent and / or SMS sent.<br><br>Copy your code and paste it in the field that says OTP below. Then click on the Login Button again, and you should be logged in if all is correct.", "otp":"Token Sent, expires: ' . $expires . '"}';
}
catch (\Exception $e) {
//var_dump($e);
echo '{"error":true,"message":'.json_encode($e->getMessage()) .'}';
}
}
protected function sendOTP(Request $request) {
$user = User::where('email', $request->email)->first();
// $test = (new AWS_SNS_SMS_Tranaction())->toSns("+16513130209");
if ($user && Hash::check($request->password, $user->password)) {
if (!empty($request->input('otp'))) {
$verify = Otp::validate($request->input('email'), $request->input('otp'));
// gets here if user/pass is correct
if ($verify->status == true) {
echo '{"error":false,"message":"LOGIN"}';
Session::put('OTP_PASS', true);
}
else if ($verify->status !== true) {
echo '{"error":false,"message":"OTP is incorrect, try again"}';
}
else {
// OTP is expired ?
$this->generateAndSendOTP($request->input('email'));
}
}
else if (empty($request->input('otp'))) {
$this->generateAndSendOTP($request->input('email'));
}
}
else {
echo '{"error":true,"message":"User Name / Password Incorect, try again"}';
}
}
and then finally in:
Providers/FortifyServiceProviders.php, I had a check for Session::get('OTP_PASS') === true to the validation for the login. It actually does seem to "work", but I'd like to possibly extend it to support sending the OTP via SMS if the user also has a phone number in the DB. I am using an e-mail for now because that is always populated in the DB. They may or may not have a Phone number, and I would make the notification method a user preference item.
I have AWS SNS setup on another framework, but not sure how to do that on Laravel. In the generateAndSendOTP($identifier) method, I would want to add something to check for their user preference notificaiton method, and then send the OTP to the email and / or via SMS. So that is one issue. The other issue is just the entire setup now because I probably need to move things around a bit to different locations now that is seems to be working. It would be nice to package up eventually to add OTP via e-mail and / or SMS. The baked in Authenticator App method should not really be affected, and I might want to use that to selectively protect certain routes after they log in via e-mail / SMS, for situations where possibly more than one user uses an account, like a proxy, but the account owner does not want them getting into sections protected with the built-in 2FA via Authenticator App.
Thanks.
Looking at the source code, Fortify actually uses PragmaRX\Google2FA so you can instantiate it directly.
So you can manually generate the current OTP that the authenticator app would generate and do whatever you need to with it. In your case, send an email (or SMS) during the 2FA challenge during login.
use PragmaRX\Google2FA\Google2FA;
...
$currentOTP = app(Google2FA::class)->getCurrentOtp(decrypt($user->two_factor_secret));
Although I think it was an oversight (as well as not requiring confirmation on initial key generation) that they didn't include this functionality built in.

Send FCM notification to multiple tokens using Plokko Firebase

I am using the Plokko FCM v1 library: https://github.com/plokko/php-fcm-v1
so far i am using a loop to send to multiple tokens, and it is becoming too slow as each loop is waiting for the notification to be sent, how to send to multiple tokens at once like we used to do it in legacy HTTP code.
i tried making it an array, a comma seperated value both times i am getting
The registration token is not a valid FCM registration token
any help would be appriciated
thank you
You must use registration_ids instead of to in creating an object and make an array of devices token and assign it to registration_ids as follow
const id = deviceIds;
const Title = "Title";
const message = "This is testing notification";
const messaged = {
registration_ids: id, //// multiple devices
// to: id, //// use for single device
collapse_key: 'appName',
notification: {
title:Title,
body: message
}
};
await fcm.send(messaged, function(err, response){
if (err) {
console.log(err)
// result(null,{success:false,message:"Some Error", data:err});
} else {
console.log(response)
}
});

Stripe PHP charge::create response

I am developing an Ionic 5 app with a PHP backend. I copied the code from the Stripe docs and put it in a try/catch as follows:
\Stripe\Stripe::setApiKey('sk_test_********************');
$postedChargeObject = file_get_contents("php://input");
$cO = json_decode($postedChargeObject);
try {
$charge = \Stripe\Charge::create([
'amount' => $cO->amount,
'currency' => 'usd',
'description' => 'charge test with token 2',
'source' => $cO->token,
]);
echo "success";
} catch(\Stripe\Error\Base $e){
echo($e->getMessage());
} catch (Exception $e){
echo $e;
}
With the echo "success", I get some unexpected behavior. 1) it takes a long time to get any response from my server and 2) I get the following error in the developer console:
If I comment out the echo "success" it still takes a few seconds but the charge goes through because I can see it on my Stripe dashboard.
Ideally, I would like my PHP script to charge the card with Stripe::create and return the charge object, as defined in the Stripe charge object docs, back to the client script. So not just echo "success" but the whole Stripe charge object.
My service for calling the PHP script is:
chargeCardForOrder(chargeObj): Observable<any> {
let c = JSON.stringify(chargeObj);
console.log("stringified c from service", c);
return this.http.post(this.chargeUrl, c);
}
My .ts file that calls the service is:
this.coService.chargeCardForOrder(cO).subscribe( resp => {
console.log( "response from server charge", resp );
})
On a side note, if I take out the echo "success" from the PHP script, the charge goes thru as per my Stripe dashboard but the console reads "response from server charge null". I am quite confused as to what I am missing. Your help would be greatly appreciated.
Your client-side code is expecting a JSON object, and you're sending it just the string success which is not valid JSON. You should likely be responding with JSON in the shape that you client-side code expects, or not returning any content at all if it's not needed client-side.

Ionic2 / Angular2 MailChimp API GET response

Okey so I've got a little problem with MailChimp response. So here is the thing.
I want to check the status of the subscribed user. I have the PHP code which is works fine and i have the code which is also works fine so I get the response BUT I can't use the response after it. So here is the codes:
I have a MailService provider which contain this function:
postCheck(post: {email: string}): Observable<any>{
const email = JSON.stringify(post);
let headers = new Headers();
headers.append('Content-Type', 'application/x-www-urlencoded');
return this.http.post('http://localhost:8100/getapi', email, {
headers: headers
}).map(res => {
console.log(res.json())
return res.json(); //add this line
});
}
In the main page I have this function:
sendCheck(email: string){
this.mailservice.postCheck({email: email})
.subscribe(
response => this.response = response.status,
error => console.log(error)
);
}
In the main page html when i call <p>{{ response }}</p> it write out 'pending' or 'subscribed'. But after it when I try console.log(this.response); it write out nothing so in the code I can't really do the checking.
From what I can gather, you want to do something with your response after the data has arrived. This you need to do inside the subscription, to ensure that the data is available. So something like this:
sendCheck(email: string){
this.mailservice.postCheck({email: email})
.subscribe(response => {
this.response = response.status;
// here the value has been set to response, so do what you want
this.doSomethingWithResponse();
)};
}
doSomethingWithResponse() {
if(this.response == 'subscribed') {
// do stuff
}
}

Categories